Find the sequence of subdomain operations over a block range (block_end is excluded).
        Does not check for validity or signature matches; only that they are well-formed.

        Optionally only finds zone file updates for a specific name

        Returns {
            'zonefile_info': [{'name':.., 'zonefile_hash':..., 'block_height':..., 'txid':..., 'subdomains': [...] or None}], # in blockchain order
            'subdomains': {'fqn': [indexes into zonefile_info]}
        }

        'subdomains' will map to a list if we had the zone file and were able to parse it.
        'subdomains' will map to None if we did not have the zone file, period (means we can't process anything for the given name beyond this point)

def find_zonefile_subdomains(self, block_start, block_end, name=None):
        """
        Find the sequence of subdomain operations over a block range (block_end is excluded).
        Does not check for validity or signature matches; only that they are well-formed.

        Optionally only finds zone file updates for a specific name

        Returns {
            'zonefile_info': [{'name':.., 'zonefile_hash':..., 'block_height':..., 'txid':..., 'subdomains': [...] or None}], # in blockchain order
            'subdomains': {'fqn': [indexes into zonefile_info]}
        }

        'subdomains' will map to a list if we had the zone file and were able to parse it.
        'subdomains' will map to None if we did not have the zone file, period (means we can't process anything for the given name beyond this point)
        """
        assert block_start < block_end
        
        subdomain_info = []
        offset = 0
        count = 100
        con = atlasdb_open(self.atlasdb_path)

        while True:
            # NOTE: filtered on name
            range_subdomain_info = atlasdb_get_zonefiles_by_block(block_start, block_end-1, offset, count, name=name, con=con)
            if len(range_subdomain_info) == 0:
                break

            offset += count
            subdomain_info += range_subdomain_info

        con.close()
       
        log.debug("Found {} zonefile hashes between {} and {} for {}".format(len(subdomain_info), block_start, block_end, '"{}"'.format(name) if name is not None else 'all names'))

        # extract sequence of subdomain operations for each zone file discovered
        for i, sdinfo in enumerate(subdomain_info):
            # find and parse zone file data
            sddata = get_atlas_zonefile_data(sdinfo['zonefile_hash'], self.zonefiles_dir)
            if sddata is None:
                # no zone file
                log.debug("Missing zonefile {} (at {})".format(sdinfo['zonefile_hash'], sdinfo['block_height']))
                subdomain_info[i]['subdomains'] = None
                continue

            subdomains = decode_zonefile_subdomains(sdinfo['name'], sddata, sdinfo['block_height'], sdinfo['inv_index'], sdinfo['txid'])
            if subdomains is None:
                # have zone file, but no subdomains
                subdomains = []

            log.debug("Found {} subdomain record(s) for '{}' in zonefile {} at {} (index {})".format(len(subdomains), sdinfo['name'], sdinfo['zonefile_hash'], sdinfo['block_height'], sdinfo['inv_index']))
            subdomain_info[i]['subdomains'] = subdomains

        # group discovered subdomain records by subdomain name
        subdomain_index = {}
        for i, zfinfo in enumerate(subdomain_info):
            if zfinfo['subdomains'] is None:
                # no zone file
                continue

            for sd in zfinfo['subdomains']:
                fqn = sd.get_fqn()
                if fqn not in subdomain_index:
                    subdomain_index[fqn] = []

                subdomain_index[fqn].append(i)

        return {'zonefile_info': subdomain_info, 'subdomains': subdomain_index}