def _get_blocks_at_height(
        blocks: BlockchainInterface,
        prev_b: BlockRecord,
        target_height: uint32,
        max_num_blocks: uint32 = uint32(1),
) -> List[BlockRecord]:
    """
    Return a consecutive list of BlockRecords starting at target_height, returning a maximum of
    max_num_blocks. Assumes all block records are present. Does a slot linear search, if the blocks are not
    in the path of the peak. Can only fetch ancestors of prev_b.

    Args:
        blocks: dict from header hash to BlockRecord.
        prev_b: prev_b (to start backwards search).
        target_height: target block to start
        max_num_blocks: max number of blocks to fetch (although less might be fetched)

    """
    if blocks.contains_height(prev_b.height):
        header_hash = blocks.height_to_hash(prev_b.height)
        if header_hash == prev_b.header_hash:
            # Efficient fetching, since we are fetching ancestor blocks within the heaviest chain. We can directly
            # use the height_to_block_record method
            block_list: List[BlockRecord] = []
            for h in range(target_height, target_height + max_num_blocks):
                assert blocks.contains_height(uint32(h))
                block_list.append(blocks.height_to_block_record(uint32(h)))
            return block_list

    # Slow fetching, goes back one by one, since we are in a fork
    curr_b: BlockRecord = prev_b
    target_blocks = []
    while curr_b.height >= target_height:
        if curr_b.height < target_height + max_num_blocks:
            target_blocks.append(curr_b)
        if curr_b.height == 0:
            break
        curr_b = blocks.block_record(curr_b.prev_hash)
    return list(reversed(target_blocks))
def _get_blocks_at_height(
        sub_blocks: BlockchainInterface,
        prev_sb: SubBlockRecord,
        target_height: uint32,
        max_num_sub_blocks: uint32 = uint32(1),
) -> List[SubBlockRecord]:
    """
    Return a consecutive list of SubBlockRecords starting at target_height, returning a maximum of
    max_num_sub_blocks. Assumes all sub-block records are present. Does a slot linear search, if the sub-blocks are not
    in the path of the peak.

    Args:
        sub_blocks: dict from header hash to SubBlockRecord.
        prev_sb: prev_sb (to start backwards search).
        target_height: target sub-block to start
        max_num_sub_blocks: max number of sub-blocks to fetch (although less might be fetched)

    """
    if sub_blocks.contains_height(prev_sb.height):
        header_hash = sub_blocks.height_to_hash(prev_sb.height)
        if header_hash == prev_sb.header_hash:
            # Efficient fetching, since we are fetching ancestor blocks within the heaviest chain
            block_list: List[SubBlockRecord] = []
            for h in range(target_height, target_height + max_num_sub_blocks):
                assert sub_blocks.contains_height(uint32(h))
                block_list.append(
                    sub_blocks.height_to_sub_block_record(uint32(h)))
            return block_list
        # slow fetching, goes back one by one
    curr_b: SubBlockRecord = prev_sb
    target_blocks = []
    while curr_b.height >= target_height:
        if curr_b.height < target_height + max_num_sub_blocks:
            target_blocks.append(curr_b)
        if curr_b.height == 0:
            break
        curr_b = sub_blocks.sub_block_record(curr_b.prev_hash)
    return list(reversed(target_blocks))