Simple name verification (snv) lookup:
    Use a known-good "current" consensus hash and block ID to
    look up a set of name operations from the past, given the previous
    point in time's untrusted block ID and consensus hash.

def snv_get_blockstack_ops_at(current_block_id, current_consensus_hash, block_id, consensus_hash, proxy=None):
    """
    Simple name verification (snv) lookup:
    Use a known-good "current" consensus hash and block ID to
    look up a set of name operations from the past, given the previous
    point in time's untrusted block ID and consensus hash.
    """
    import blockstack

    log.debug('verify {}-{} to {}-{}'.format(
        current_block_id, current_consensus_hash, block_id, consensus_hash
    ))

    proxy = get_default_proxy() if proxy is None else proxy

    # work backwards in time, using a Merkle skip-list constructed
    # by blockstackd over the set of consensus hashes.
    next_block_id = current_block_id

    prev_blockstack_ops_hashes = {}
    prev_consensus_hashes = {
        next_block_id: current_consensus_hash
    }

    # print 'next_block_id = {}, block_id = {}'.format(next_block_id, block_id)
    while next_block_id >= block_id:
        # get blockstack_ops_at[ next_block_id ], and all consensus_hash[ next_block_id - 2^i ]
        # such that block_id - 2*i > block_id (start at i = 1)
        i = 0
        blockstack_ops_hash = None

        if next_block_id in prev_blockstack_ops_hashes:
            blockstack_ops_hash = prev_blockstack_ops_hashes[next_block_id]
        else:
            blockstack_ops_resp = get_blockstack_ops_hash_at(next_block_id, proxy=proxy)

            if 'error' in blockstack_ops_resp:
                log.error('get_blockstack_ops_hash_at: {}'.format(blockstack_ops_resp['error']))
                return {'error': 'Failed to get nameops: {}'.format(blockstack_ops_resp['error'])}

            blockstack_ops_hash = str(blockstack_ops_resp)
            prev_blockstack_ops_hashes[next_block_id] = blockstack_ops_hash

        log.debug('nameops hash at {}: {}'.format(next_block_id, blockstack_ops_hash))

        # find out which consensus hashes we'll need
        to_fetch = []
        ch_block_ids = []
        while next_block_id - (2 ** (i + 1) - 1) >= FIRST_BLOCK_MAINNET:
            i += 1
            prev_block_id = next_block_id - (2 ** i - 1)
            ch_block_ids.append(prev_block_id)

            if prev_block_id not in prev_consensus_hashes:
                to_fetch.append(prev_block_id)

        # get the consensus hashes
        chs = {}
        if to_fetch:
            chs = get_consensus_hashes(to_fetch, proxy=proxy)
            if 'error' in chs:
                msg = 'Failed to get consensus hashes for {}: {}'
                log.error(msg.format(to_fetch, chs['error']))
                return {'error': 'Failed to get consensus hashes'}

        prev_consensus_block_ids = []
        for b in ch_block_ids:
            # NOTE: we process to_fetch *in decreasing order* so we know when we're missing data
            if b not in chs and b not in prev_consensus_hashes:
                msg = 'Missing consensus hash response for {} (chs={}, prev_chs={})'
                log.error(msg.format(b, chs, prev_consensus_hashes))
                return {'error': 'Server did not reply valid data'}

            prev_consensus_block_ids.append(b)
            if b in prev_consensus_hashes:
                # already got this one
                continue

            ch = chs[b]
            if ch is not None:
                prev_consensus_hashes[b] = str(ch)
            else:
                # no consensus hash for this block and all future blocks
                prev_consensus_block_ids.pop()
                break

        # prev_consensus_hashes_list = [ prev_consensus_hashes[b] for b in ch_block_ids ]
        prev_consensus_hashes_list = [
            prev_consensus_hashes[b] for b in prev_consensus_block_ids
        ]

        # calculate the snapshot, and see if it matches
        ch = virtualchain.StateEngine.make_snapshot_from_ops_hash(
            blockstack_ops_hash, prev_consensus_hashes_list
        )

        expected_ch = prev_consensus_hashes[next_block_id]
        if ch != expected_ch:
            msg = 'Consensus hash mismatch at {}: expected {}, got {} (from {}, {})'
            log.error(msg.format(next_block_id, expected_ch, ch, blockstack_ops_hash, prev_consensus_hashes))
            return {'error': 'Consensus hash mismatch'}

        # advance!
        # find the smallest known consensus hash whose block is greater than block_id
        current_candidate = next_block_id
        found_any = False
        for candidate_block_id in prev_consensus_hashes:
            if candidate_block_id < block_id:
                continue

            if candidate_block_id < current_candidate:
                current_candidate = candidate_block_id
                found_any = True

        if not found_any:
            break

        next_block_id = current_candidate

    # get the final nameops
    historic_nameops = get_blockstack_transactions_at(block_id, proxy=proxy)
    if isinstance(historic_nameops, dict) and 'error' in historic_nameops:
        log.error('Failed to get nameops at {}: {}'.format(block_id, historic_nameops['error']))
        return {'error': 'BUG: no nameops found'}

    log.debug("nameops at {}\n{}".format(block_id, json.dumps(historic_nameops, sort_keys=True)))

    # sanity check...
    for historic_op in historic_nameops:
        if 'opcode' not in historic_op:
            return {'error': 'Invalid/corrupt name operations detected'}

        # recover binary op string
        if 'op' not in historic_op:
            historic_op['op'] = NAME_OPCODES[str(historic_op['opcode'])]

    # check integrity
    opfields = blockstack.lib.virtualchain_hooks.get_opfields()
    serialized_historic_nameops = [
        virtualchain.StateEngine.serialize_op(
            str(op['op'][0]), op, opfields, verbose=True
        ) for op in historic_nameops
    ]

    historic_blockstack_ops_hash = virtualchain.StateEngine.make_ops_snapshot(serialized_historic_nameops)

    if block_id not in prev_blockstack_ops_hashes:
        return {'error': 'Previous block/consensus hash is unreachable from trusted block/consensus hash'}

    if historic_blockstack_ops_hash != prev_blockstack_ops_hashes[block_id]:
        log.error("historic nameops hash at {}: {}".format(block_id, historic_blockstack_ops_hash))
        log.error("prev_blockstack_ops_hashes:\n{}".format(json.dumps(prev_blockstack_ops_hashes, indent=4, sort_keys=True)))
        return {
            'error': 'Hash mismatch: failed to get operations at {}-{} from {}-{} ({} != {})'.format(
                block_id, consensus_hash, current_block_id, current_consensus_hash, historic_blockstack_ops_hash, prev_blockstack_ops_hashes[block_id]
            )
        }

    log.debug('{} nameops at {}'.format(len(historic_nameops), block_id))

    # strip history
    for hn in historic_nameops:
        if 'history' in hn.keys():
            del hn['history']

    return historic_nameops