Fix issues with empty slots after epochs

This commit is contained in:
Mariano Sorgente 2020-11-26 17:35:35 +09:00 committed by Yostra
parent eb1cddbee4
commit 0cf7eb7850
10 changed files with 224 additions and 184 deletions

View File

@ -197,6 +197,7 @@ def create_foliage(
def create_unfinished_block(
constants: ConsensusConstants,
sub_slot_start_total_iters: uint128,
sub_slot_iters: uint64,
signage_point_index: uint8,
sp_iters: uint64,
ip_iters: uint64,
@ -221,12 +222,10 @@ def create_unfinished_block(
if prev_sub_block is not None:
curr = prev_sub_block
is_block, prev_block = get_prev_block(curr, prev_block, sub_blocks, total_iters_sp)
prev_sub_slot_iters: uint64 = prev_sub_block.sub_slot_iters
else:
# Genesis is a block
is_genesis = True
is_block = True
prev_sub_slot_iters = constants.SUB_SLOT_ITERS_STARTING
new_sub_slot: bool = len(finished_sub_slots) > 0
@ -255,7 +254,7 @@ def create_unfinished_block(
assert rc_sp_signature is not None
assert blspy.AugSchemeMPL.verify(proof_of_space.plot_public_key, cc_sp_hash, cc_sp_signature)
total_iters = uint128(sub_slot_start_total_iters + ip_iters + (prev_sub_slot_iters if overflow else 0))
total_iters = uint128(sub_slot_start_total_iters + ip_iters + (sub_slot_iters if overflow else 0))
rc_sub_block = RewardChainSubBlockUnfinished(
total_iters,

View File

@ -218,7 +218,7 @@ async def validate_unfinished_header_block(
if sub_slot.challenge_chain.subepoch_summary_hash is not None:
return None, ValidationError(Err.INVALID_SUB_EPOCH_SUMMARY_HASH)
if can_finish_epoch:
if can_finish_epoch and sub_slot.challenge_chain.subepoch_summary_hash is not None:
# 2m. Check new difficulty and ssi
if sub_slot.challenge_chain.new_sub_slot_iters != sub_slot_iters:
return None, ValidationError(Err.INVALID_NEW_SUB_SLOT_ITERS)
@ -357,7 +357,7 @@ async def validate_unfinished_header_block(
)
elif new_sub_slot and not genesis_block:
# 3d. Check that we don't have to include a sub-epoch summary
if can_finish_se:
if can_finish_se or can_finish_epoch:
return None, ValidationError(
Err.INVALID_SUB_EPOCH_SUMMARY, "block finishes sub-epoch but ses-hash is None"
)

View File

@ -1045,6 +1045,7 @@ class FullNode:
unfinished_block: Optional[UnfinishedBlock] = create_unfinished_block(
self.constants,
total_iters_pos_slot,
sub_slot_iters,
request.signage_point_index,
sp_iters,
ip_iters,

View File

@ -57,23 +57,6 @@ class FullNodeStore:
self.initialize_genesis_sub_slot()
return self
def add_disconnected_block(self, block: FullBlock) -> None:
self.disconnected_blocks[block.header_hash] = block
def get_disconnected_block_by_prev(self, prev_header_hash: bytes32) -> Optional[FullBlock]:
for _, block in self.disconnected_blocks.items():
if block.prev_header_hash == prev_header_hash:
return block
return None
def get_disconnected_block(self, header_hash: bytes32) -> Optional[FullBlock]:
return self.disconnected_blocks.get(header_hash, None)
def clear_disconnected_blocks_below(self, height: uint32) -> None:
for key in list(self.disconnected_blocks.keys()):
if self.disconnected_blocks[key].height < height:
del self.disconnected_blocks[key]
def add_candidate_block(
self,
quality_string: bytes32,
@ -95,12 +78,6 @@ class FullNodeStore:
except KeyError:
pass
def add_unfinished_block(self, unfinished_block: UnfinishedBlock) -> None:
self.unfinished_blocks[unfinished_block.reward_chain_sub_block.get_hash()] = unfinished_block
def get_unfinished_block(self, unfinished_reward_hash: bytes32) -> Optional[UnfinishedBlock]:
return self.unfinished_blocks.get(unfinished_reward_hash, None)
def seen_unfinished_block(self, temp_header_hash: bytes32) -> bool:
if temp_header_hash in self.seen_unfinished_blocks:
return True
@ -110,6 +87,29 @@ class FullNodeStore:
def clear_seen_unfinished_blocks(self) -> None:
self.seen_unfinished_blocks.clear()
def add_disconnected_block(self, block: FullBlock) -> None:
self.disconnected_blocks[block.header_hash] = block
def get_disconnected_block_by_prev(self, prev_header_hash: bytes32) -> Optional[FullBlock]:
for _, block in self.disconnected_blocks.items():
if block.prev_header_hash == prev_header_hash:
return block
return None
def get_disconnected_block(self, header_hash: bytes32) -> Optional[FullBlock]:
return self.disconnected_blocks.get(header_hash, None)
def clear_disconnected_blocks_below(self, height: uint32) -> None:
for key in list(self.disconnected_blocks.keys()):
if self.disconnected_blocks[key].height < height:
del self.disconnected_blocks[key]
def add_unfinished_block(self, unfinished_block: UnfinishedBlock) -> None:
self.unfinished_blocks[unfinished_block.reward_chain_sub_block.get_hash()] = unfinished_block
def get_unfinished_block(self, unfinished_reward_hash: bytes32) -> Optional[UnfinishedBlock]:
return self.unfinished_blocks.get(unfinished_reward_hash, None)
def get_unfinished_blocks(self) -> Dict[bytes32, UnfinishedBlock]:
return self.unfinished_blocks
@ -146,7 +146,7 @@ class FullNodeStore:
def new_finished_sub_slot(
self, eos: EndOfSubSlotBundle, sub_blocks: Dict[bytes32, SubBlockRecord], peak: Optional[SubBlockRecord]
):
) -> bool:
"""
Returns true if finished slot successfully added.
TODO: do full validation here
@ -357,9 +357,7 @@ class FullNodeStore:
self,
peak: SubBlockRecord,
peak_sub_slot: Optional[EndOfSubSlotBundle], # None if in first slot
total_iters: uint128,
prev_sub_slot: Optional[EndOfSubSlotBundle], # None if not overflow, or in first/second slot
prev_sub_slot_total_iters: Optional[uint128], # None if not overflow
reorg: bool,
sub_blocks: Dict[bytes32, SubBlockRecord],
) -> Optional[EndOfSubSlotBundle]:
@ -368,10 +366,11 @@ class FullNodeStore:
the prev sub-slot (since we still might get more sub-blocks with an sp in the previous sub-slot)
"""
new_finished_sub_slots = []
total_iters = peak.infusion_sub_slot_total_iters(self.constants)
if not reorg:
# This is a new peak that adds to the last peak. We can clear data in old sub-slots. (and new ones)
for index, (sub_slot, sps, total_iters) in enumerate(self.finished_sub_slots):
if prev_sub_slot_total_iters is not None:
if peak.overflow and prev_sub_slot is not None:
if sub_slot == prev_sub_slot:
# In the case of a peak overflow sub-block, the previous sub-slot is added
new_finished_sub_slots.append((sub_slot, sps, total_iters))
@ -384,7 +383,8 @@ class FullNodeStore:
# This is either a reorg, which means some sub-blocks are reverted, or this sub slot is not in our current
# cache, delete the entire cache and add this sub slot.
self.clear_slots()
if prev_sub_slot_total_iters is not None:
if peak.overflow and prev_sub_slot is not None:
prev_sub_slot_total_iters = peak.pos_sub_slot_total_iters(self.constants)
self.finished_sub_slots = [(prev_sub_slot, {}, prev_sub_slot_total_iters)]
self.finished_sub_slots.append((peak_sub_slot, {}, total_iters))
@ -398,7 +398,7 @@ class FullNodeStore:
self,
prev_sb: Optional[SubBlockRecord],
sub_block_records: Dict[bytes32, SubBlockRecord],
pos_challenge_hash: bytes32,
pos_ss_challenge_hash: bytes32,
extra_sub_slot: bool = False,
) -> List[EndOfSubSlotBundle]:
"""
@ -424,13 +424,13 @@ class FullNodeStore:
raise ValueError("First sub slot should be None")
final_index = 0
for index, (sub_slot, sps, total_iters) in enumerate(self.finished_sub_slots):
if sub_slot is not None and sub_slot.challenge_chain.get_hash() == pos_challenge_hash:
if sub_slot is not None and sub_slot.challenge_chain.get_hash() == pos_ss_challenge_hash:
pos_index = index
else:
for index, (sub_slot, sps, total_iters) in enumerate(self.finished_sub_slots):
if sub_slot is None:
pass
if sub_slot.challenge_chain.get_hash() == pos_challenge_hash:
if sub_slot.challenge_chain.get_hash() == pos_ss_challenge_hash:
pos_index = index
if sub_slot.challenge_chain.get_hash() == final_sub_slot_in_chain:
final_index = index

View File

@ -26,21 +26,12 @@ class UnfinishedBlock(Streamable):
def prev_header_hash(self):
return self.foliage_sub_block.prev_sub_block_hash
@property
def height(self):
return self.reward_chain_sub_block.sub_block_height
def partial_hash(self):
return self.reward_chain_sub_block.get_hash()
@property
def weight(self):
return self.reward_chain_sub_block.weight
def is_block(self):
return self.foliage_sub_block.foliage_block_hash is not None
@property
def total_iters(self):
return self.reward_chain_sub_block.total_iters
@property
def header_hash(self):
return self.foliage_sub_block.get_hash()
def is_block(self):
return self.foliage_sub_block.foliage_block_hash is not None

View File

@ -70,7 +70,7 @@ test_constants = DEFAULT_CONSTANTS.replace(
"DISCRIMINANT_SIZE_BITS": 16,
"SUB_EPOCH_SUB_BLOCKS": 140,
"EPOCH_SUB_BLOCKS": 280,
"SUB_SLOT_ITERS_STARTING": 2 ** 12, # Must be a multiple of 64
"SUB_SLOT_ITERS_STARTING": 2 ** 10, # Must be a multiple of 64
"NUMBER_ZERO_BITS_PLOT_FILTER": 1, # H(plot signature of the challenge) must start with these many zeroes
"MAX_FUTURE_TIME": 3600
* 24
@ -148,7 +148,7 @@ class BlockTools:
mkdir(temp_dir)
args = Namespace()
# Can't go much lower than 20, since plots start having no solutions and more buggy
args.size = 20
args.size = 22
# Uses many plots for testing, in order to guarantee proofs of space at every height
args.num = 20
args.buffer = 100
@ -162,7 +162,7 @@ class BlockTools:
args.buckets = 0
args.stripe_size = 2000
args.num_threads = 0
test_private_keys = [AugSchemeMPL.key_gen(std_hash(i.to_bytes(3, "big"))) for i in range(args.num)]
test_private_keys = [AugSchemeMPL.key_gen(std_hash(i.to_bytes(2, "big"))) for i in range(args.num)]
try:
# No datetime in the filename, to get deterministic filenames and not re-plot
create_plots(
@ -209,7 +209,7 @@ class BlockTools:
def get_consecutive_blocks(
self,
num_blocks: uint8,
block_list: List[FullBlock] = None,
block_list_input: List[FullBlock] = None,
farmer_reward_puzzle_hash: Optional[bytes32] = None,
pool_reward_puzzle_hash: Optional[bytes32] = None,
transaction_data: Optional[SpendBundle] = None,
@ -218,7 +218,10 @@ class BlockTools:
force_overflow: bool = False,
skip_slots: uint32 = uint32(0), # Force at least this number of empty slots before the first SB
) -> List[FullBlock]:
if block_list_input is not None:
block_list = block_list_input.copy()
else:
block_list = []
constants = self.constants
transaction_data_included = False
if time_per_sub_block is None:
@ -230,7 +233,7 @@ class BlockTools:
pool_reward_puzzle_hash = self.pool_ph
pool_target = PoolTarget(pool_reward_puzzle_hash, uint32(0))
if block_list is None or len(block_list) == 0:
if len(block_list) == 0:
initial_block_list_len = 0
genesis = self.create_genesis_block(
constants,
@ -272,6 +275,7 @@ class BlockTools:
same_slot_as_last = True # Only applies to first slot, to prevent old blocks from being added
sub_slot_start_total_iters: uint128 = latest_sub_block.infusion_sub_slot_total_iters(constants)
sub_slots_finished = 0
pending_ses: bool = False
# Start at the last block in block list
# Get the challenge for that slot
@ -361,12 +365,10 @@ class BlockTools:
latest_sub_block,
sub_blocks,
)
if full_block is None:
continue
if sub_block_record.is_block:
transaction_data_included = True
if pending_ses:
pending_ses = False
block_list.append(full_block)
sub_blocks_added_this_sub_slot += 1
@ -425,14 +427,18 @@ class BlockTools:
# in order for light clients to validate.
cc_vdf = VDFInfo(cc_vdf.challenge, ClassgroupElement.get_default_element(), sub_slot_iters, cc_vdf.output)
sub_epoch_summary: Optional[SubEpochSummary] = next_sub_epoch_summary(
constants,
sub_blocks,
height_to_hash,
latest_sub_block.signage_point_index,
latest_sub_block.required_iters,
block_list[-1],
)
if pending_ses:
sub_epoch_summary: Optional[SubEpochSummary] = None
else:
sub_epoch_summary: Optional[SubEpochSummary] = next_sub_epoch_summary(
constants,
sub_blocks,
height_to_hash,
latest_sub_block.signage_point_index,
latest_sub_block.required_iters,
block_list[-1],
)
pending_ses = True
if sub_epoch_summary is not None:
ses_hash = sub_epoch_summary.get_hash()
@ -564,10 +570,10 @@ class BlockTools:
overflow_rc_challenge=overflow_rc_challenge,
)
if full_block is None:
continue
if sub_block_record.is_block:
transaction_data_included = True
if pending_ses:
pending_ses = False
block_list.append(full_block)
sub_blocks_added_this_sub_slot += 1
@ -655,6 +661,7 @@ class BlockTools:
unfinished_block = create_unfinished_block(
constants,
sub_slot_total_iters,
constants.SUB_SLOT_ITERS_STARTING,
uint8(signage_point_index),
sp_iters,
ip_iters,
@ -1086,10 +1093,10 @@ def get_full_block_and_sub_record(
) -> Tuple[FullBlock, SubBlockRecord]:
sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index)
ip_iters = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters)
unfinished_block = create_unfinished_block(
constants,
sub_slot_start_total_iters,
sub_slot_iters,
signage_point_index,
sp_iters,
ip_iters,

View File

@ -41,6 +41,16 @@ async def default_1000_blocks():
yield persistent_blocks(1000, "test_blocks_1000.db")
@pytest.fixture(scope="module")
async def default_10000_blocks():
yield persistent_blocks(10000, "test_blocks_10000.db")
@pytest.fixture(scope="module")
async def default_20000_blocks():
yield persistent_blocks(20000, "test_blocks_20000.db")
def persistent_blocks(num_of_blocks, db_name):
# try loading from disc, if not create new blocks.db file
if path.exists(db_name):

View File

@ -17,7 +17,7 @@ from src.util.ints import uint64, uint8, int512
from tests.recursive_replace import recursive_replace
from tests.setup_nodes import test_constants, bt
from tests.full_node.fixtures import empty_blockchain
from tests.full_node.fixtures import default_400_blocks as blocks
from tests.full_node.fixtures import default_1000_blocks
@pytest.fixture(scope="module")
@ -76,8 +76,8 @@ class TestGenesisBlock:
class TestBlockHeaderValidation:
@pytest.mark.asyncio
async def test_long_chain(self, empty_blockchain, blocks):
# blocks = bt.get_consecutive_blocks(400)
async def test_long_chain(self, empty_blockchain, default_1000_blocks):
blocks = default_1000_blocks
for block in blocks:
# if (
# len(block.finished_sub_slots) > 0
@ -165,7 +165,7 @@ class TestBlockHeaderValidation:
result, err, _ = await blockchain.receive_block(block)
assert result == ReceiveBlockResult.NEW_PEAK
blocks = bt.get_consecutive_blocks(10, skip_slots=2, block_list=blocks)
blocks = bt.get_consecutive_blocks(10, skip_slots=2, block_list_input=blocks)
for block in blocks[10:]:
result, err, _ = await blockchain.receive_block(block)
assert err is None
@ -177,7 +177,7 @@ class TestBlockHeaderValidation:
num_blocks = 20
blocks = []
for i in range(num_blocks):
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=1)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=1)
result, err, _ = await blockchain.receive_block(blocks[-1])
assert result == ReceiveBlockResult.NEW_PEAK
assert blockchain.get_peak().height == num_blocks - 1
@ -188,7 +188,7 @@ class TestBlockHeaderValidation:
num_blocks = 20
blocks = []
for i in range(num_blocks): # Same thing, but 2 sub-slots per sub-block
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=2)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=2)
result, err, _ = await blockchain.receive_block(blocks[-1])
assert result == ReceiveBlockResult.NEW_PEAK
assert blockchain.get_peak().height == num_blocks - 1
@ -199,7 +199,7 @@ class TestBlockHeaderValidation:
num_blocks = 10
blocks = []
for i in range(num_blocks): # Same thing, but 5 sub-slots per sub-block
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=5)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=5)
result, err, _ = await blockchain.receive_block(blocks[-1])
assert result == ReceiveBlockResult.NEW_PEAK
assert blockchain.get_peak().height == num_blocks - 1
@ -220,7 +220,7 @@ class TestBlockHeaderValidation:
num_blocks = 10
blocks = []
for i in range(num_blocks):
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=2, force_overflow=True)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=2, force_overflow=True)
result, err, _ = await blockchain.receive_block(blocks[-1])
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
@ -269,7 +269,7 @@ class TestBlockHeaderValidation:
async def test_invalid_sub_slot_challenge_hash_non_genesis(self, empty_blockchain):
# 2b
blocks = bt.get_consecutive_blocks(1, force_overflow=False, skip_slots=0)
blocks = bt.get_consecutive_blocks(1, force_overflow=False, skip_slots=1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, force_overflow=False, skip_slots=1, block_list_input=blocks)
print(blocks)
new_finished_ss = recursive_replace(
blocks[1].finished_sub_slots[0],
@ -289,7 +289,7 @@ class TestBlockHeaderValidation:
async def test_invalid_sub_slot_challenge_hash_empty_ss(self, empty_blockchain):
# 2c
blocks = bt.get_consecutive_blocks(1, force_overflow=False, skip_slots=0)
blocks = bt.get_consecutive_blocks(1, force_overflow=False, skip_slots=2, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, force_overflow=False, skip_slots=2, block_list_input=blocks)
new_finished_ss = recursive_replace(
blocks[1].finished_sub_slots[-1],
"challenge_chain.challenge_chain_end_of_slot_vdf.challenge",
@ -433,7 +433,7 @@ class TestBlockHeaderValidation:
assert (await blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
case_1, case_2 = False, False
while not case_1 or not case_2:
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=1)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=1)
block = blocks[-1]
if len(block.finished_sub_slots) > 0 and block.finished_sub_slots[-1].infused_challenge_chain is not None:
if (
@ -522,7 +522,7 @@ class TestBlockHeaderValidation:
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(1)
assert (await blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=4)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=4)
new_finished_ss = recursive_replace(
blocks[-1].finished_sub_slots[-1],
@ -535,12 +535,30 @@ class TestBlockHeaderValidation:
result, err, _ = await blockchain.receive_block(block_bad)
assert err == Err.INVALID_SUB_EPOCH_SUMMARY_HASH
@pytest.mark.asyncio
async def test_empty_sub_slots_epoch(self, empty_blockchain):
# 2m
# Tests adding an empty sub slot after the sub-epoch / epoch.
# Also tests overflow block in epoch
blocks_base = bt.get_consecutive_blocks(test_constants.EPOCH_SUB_BLOCKS)
blocks_1 = bt.get_consecutive_blocks(1, block_list_input=blocks_base, force_overflow=True)
blocks_2 = bt.get_consecutive_blocks(1, skip_slots=1, block_list_input=blocks_base, force_overflow=True)
blocks_3 = bt.get_consecutive_blocks(1, skip_slots=2, block_list_input=blocks_base, force_overflow=True)
blocks_4 = bt.get_consecutive_blocks(1, block_list_input=blocks_base)
for block in blocks_base:
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
for block in [blocks_1[-1], blocks_2[-1], blocks_3[-1], blocks_4[-1]]:
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
@pytest.mark.asyncio
async def test_wrong_cc_hash_rc(self, empty_blockchain):
# 2o
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(1, skip_slots=1)
blocks = bt.get_consecutive_blocks(1, skip_slots=1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, skip_slots=1, block_list_input=blocks)
assert (await blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
new_finished_ss = recursive_replace(
@ -756,7 +774,7 @@ class TestBlockHeaderValidation:
await empty_blockchain.receive_block(blocks[1])
case_1, case_2 = False, False
while not case_1 or not case_2:
blocks = bt.get_consecutive_blocks(1, block_list=blocks, skip_slots=1)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=1)
if len(blocks[-1].finished_sub_slots) > 0:
new_finished_ss = recursive_replace(
blocks[-1].finished_sub_slots[-1],
@ -813,7 +831,7 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if len(blocks[-1].finished_sub_slots) > 0:
new_finished_ss: EndOfSubSlotBundle = recursive_replace(
blocks[-1].finished_sub_slots[0],
@ -907,7 +925,7 @@ class TestBlockHeaderValidation:
blocks = []
case_1, case_2 = False, False
while not case_1 or not case_2:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].reward_chain_sub_block.signage_point_index == 0:
case_1 = True
block_bad = recursive_replace(blocks[-1], "reward_chain_sub_block.signage_point_index", uint8(1))
@ -942,7 +960,7 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].reward_chain_sub_block.signage_point_index != 0:
block_bad = recursive_replace(
blocks[-1], "reward_chain_sub_block.reward_chain_sp_vdf.challenge", std_hash(b"1")
@ -992,7 +1010,7 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].reward_chain_sub_block.signage_point_index != 0:
block_bad = recursive_replace(
blocks[-1], "reward_chain_sub_block.challenge_chain_sp_vdf.challenge", std_hash(b"1")
@ -1057,7 +1075,7 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].foliage_block is not None:
block_bad = recursive_replace(
blocks[-1], "foliage_sub_block.foliage_block_signature", G2Element.generator()
@ -1114,7 +1132,7 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
case_1, case_2 = False, False
while not case_1 or not case_2:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].foliage_block is not None:
case_1 = True
block_bad: FullBlock = recursive_replace(blocks[-1], "foliage_sub_block.foliage_block_hash", None)
@ -1134,7 +1152,7 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
case_1, case_2 = False, False
while not case_1 or not case_2:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].foliage_block is not None:
block_bad: FullBlock = recursive_replace(
blocks[-1], "foliage_sub_block.foliage_block_hash", std_hash(b"2")
@ -1168,7 +1186,7 @@ class TestBlockHeaderValidation:
blocks = bt.get_consecutive_blocks(1)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].foliage_block is not None:
block_bad: FullBlock = recursive_replace(blocks[-1], "foliage_block.prev_block_hash", std_hash(b"2"))
block_bad: FullBlock = recursive_replace(
@ -1189,7 +1207,7 @@ class TestBlockHeaderValidation:
blocks = bt.get_consecutive_blocks(1)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].foliage_block is not None:
block_bad: FullBlock = recursive_replace(blocks[-1], "foliage_block.filter_hash", std_hash(b"2"))
block_bad: FullBlock = recursive_replace(
@ -1210,7 +1228,7 @@ class TestBlockHeaderValidation:
blocks = bt.get_consecutive_blocks(1)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if blocks[-1].foliage_block is not None:
block_bad: FullBlock = recursive_replace(
blocks[-1], "foliage_block.timestamp", blocks[0].foliage_block.timestamp - 10
@ -1276,7 +1294,7 @@ class TestBlockHeaderValidation:
blocks = bt.get_consecutive_blocks(1)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
block_bad = recursive_replace(
blocks[-1], "reward_chain_sub_block.challenge_chain_ip_vdf.challenge", std_hash(b"1")
)
@ -1312,7 +1330,7 @@ class TestBlockHeaderValidation:
blocks = bt.get_consecutive_blocks(1)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
block_bad = recursive_replace(
blocks[-1], "reward_chain_sub_block.reward_chain_ip_vdf.challenge", std_hash(b"1")
)
@ -1348,7 +1366,7 @@ class TestBlockHeaderValidation:
blocks = bt.get_consecutive_blocks(1)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
block_bad = recursive_replace(
blocks[-1], "reward_chain_sub_block.infused_challenge_chain_ip_vdf.challenge", std_hash(b"1")
)
@ -1399,7 +1417,7 @@ class TestBlockHeaderValidation:
# Test one which should not be a block
while True:
blocks = bt.get_consecutive_blocks(1, block_list=blocks)
blocks = bt.get_consecutive_blocks(1, block_list_input=blocks)
if not blocks[-1].is_block():
block_bad: FullBlock = recursive_replace(blocks[-1], "reward_chain_sub_block.is_block", True)
block_bad: FullBlock = recursive_replace(

View File

@ -717,7 +717,7 @@ class TestWalletProtocol:
blocks_new = bt.get_consecutive_blocks(
test_constants,
1,
block_list=blocks_list,
block_list_input=blocks_list,
seed=b"test_request_additions",
)
async for _ in full_node_1.respond_block(fnp.RespondBlock(blocks_new[-1])):
@ -879,7 +879,7 @@ class TestWalletProtocol:
blocks_new = bt.get_consecutive_blocks(
test_constants,
10,
block_list=blocks_list,
block_list_input=blocks_list,
)
for block in blocks_new:
[_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]
@ -913,7 +913,7 @@ class TestWalletProtocol:
)
}
blocks_new = bt.get_consecutive_blocks(
test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h
test_constants, 5, block_list_input=blocks_new, transaction_data_at_height=dic_h
)
for block in blocks_new:
[_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]
@ -1053,7 +1053,7 @@ class TestWalletProtocol:
blocks_new = bt.get_consecutive_blocks(
test_constants,
10,
block_list=blocks_list,
block_list_input=blocks_list,
)
for block in blocks_new:
[_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]
@ -1088,7 +1088,7 @@ class TestWalletProtocol:
)
}
blocks_new = bt.get_consecutive_blocks(
test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h
test_constants, 5, block_list_input=blocks_new, transaction_data_at_height=dic_h
)
for block in blocks_new:
[_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]

View File

@ -5,8 +5,11 @@ import sqlite3
import aiosqlite
import pytest
from pytest import raises
from src.full_node.full_node_store import FullNodeStore
from src.types.sized_bytes import bytes32
from src.types.unfinished_block import UnfinishedBlock
from src.util.ints import uint32, uint64
from tests.setup_nodes import test_constants, bt
@ -19,97 +22,108 @@ def event_loop():
class TestFullNodeStore:
@pytest.mark.asyncio
async def test_basic_store(self):
assert sqlite3.threadsafety == 1
blocks = bt.get_consecutive_blocks(test_constants, 9, [], 9, b"0")
# blocks_alt = bt.get_consecutive_blocks(test_constants, 3, [], 9, b"1")
bt.get_consecutive_blocks(test_constants, 3, [], 9, b"1")
db_filename = Path("blockchain_test.db")
db_filename_2 = Path("blockchain_test_2.db")
db_filename_3 = Path("blockchain_test_3.db")
async def test_basic_store(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(test_constants, 10)
if db_filename.exists():
db_filename.unlink()
if db_filename_2.exists():
db_filename_2.unlink()
if db_filename_3.exists():
db_filename_3.unlink()
store = await FullNodeStore.create(test_constants)
connection = await aiosqlite.connect(db_filename)
connection_2 = await aiosqlite.connect(db_filename_2)
connection_3 = await aiosqlite.connect(db_filename_3)
db = await FullNodeStore.create(connection)
db_2 = await FullNodeStore.create(connection_2)
try:
# Add/get candidate block
assert db.get_candidate_block(0) is None
partial = (
blocks[5].transactions_generator,
blocks[5].transactions_filter,
blocks[5].header.data,
blocks[5].proof_of_space,
unfinished_blocks = []
for block in blocks:
unfinished_blocks.append(
UnfinishedBlock(
block.finished_sub_slots,
block.reward_chain_sub_block.get_unfinished(),
block.challenge_chain_sp_proof,
block.reward_chain_sp_proof,
block.foliage_sub_block,
block.foliage_block,
block.transactions_info,
block.transactions_generator,
)
)
db.add_candidate_block(blocks[5].header_hash, *partial)
assert db.get_candidate_block(blocks[5].header_hash) == partial
db.clear_candidate_blocks_below(uint32(8))
assert db.get_candidate_block(blocks[5].header_hash) is None
# Proof of time heights
chall_iters = (bytes32(bytes([1] * 32)), uint32(32532535))
chall_iters_2 = (bytes32(bytes([3] * 32)), uint32(12522535))
assert db.get_proof_of_time_heights(chall_iters) is None
db.add_proof_of_time_heights(chall_iters, 5)
db.add_proof_of_time_heights(chall_iters_2, 7)
db.clear_proof_of_time_heights_below(6)
assert db.get_proof_of_time_heights(chall_iters) is None
assert db.get_proof_of_time_heights(chall_iters_2) is not None
# Add/get candidate block
assert store.get_candidate_block(unfinished_blocks[0].get_hash()) is None
for unf_block in unfinished_blocks:
store.add_candidate_block(unf_block.get_hash(), unf_block)
# Add/get unfinished block
i = 1
for block in blocks:
key = (block.header_hash, uint64(1000))
assert store.get_candidate_block(unfinished_blocks[4].get_hash()) == unfinished_blocks[4]
store.clear_candidate_blocks_below(uint32(8))
assert store.get_candidate_block(unfinished_blocks[5].get_hash()) is None
assert store.get_candidate_block(unfinished_blocks[8].get_hash()) is not None
# Different database should have different data
await db_2.add_unfinished_block(key, block)
# Test seen unfinished blocks
h_hash_1 = bytes32(token_bytes(32))
assert not store.seen_unfinished_block(h_hash_1)
assert store.seen_unfinished_block(h_hash_1)
await store.clear_seen_unfinished_blocks()
assert not store.seen_unfinished_block(h_hash_1)
assert await db.get_unfinished_block(key) is None
await db.add_unfinished_block(key, block)
assert await db.get_unfinished_block(key) == block
assert len(await db.get_unfinished_blocks()) == i
i += 1
await db.clear_unfinished_blocks_below(uint32(5))
assert len(await db.get_unfinished_blocks()) == 5
# Disconnected blocks
assert store.get_disconnected_block(blocks[0].prev_header_hash) is None
for block in blocks:
store.add_disconnected_block(block)
assert store.get_disconnected_block_by_prev(block.prev_header_hash) == block
assert store.get_disconnected_block(block.header_hash) == block
assert db.get_disconnected_block(blocks[0].prev_header_hash) is None
# Disconnected blocks
for block in blocks:
db.add_disconnected_block(block)
db.get_disconnected_block(block.prev_header_hash) == block
# Add/get unfinished block
for unf_block in unfinished_blocks:
assert store.get_unfinished_block(unf_block.partial_hash) is None
store.add_unfinished_block(unf_block)
assert store.get_unfinished_block(unf_block.partial_hash) == unf_block
store.remove_unfinished_block(unf_block.partial_hash)
assert store.get_unfinished_block(unf_block.partial_hash) is None
db.clear_disconnected_blocks_below(uint32(5))
assert db.get_disconnected_block(blocks[4].prev_header_hash) is None
blocks = bt.get_consecutive_blocks(test_constants, 1, skip_slots=5)
sub_slots = blocks[0].finished_sub_slots
assert len(sub_slots) == 5
h_hash_1 = bytes32(token_bytes(32))
assert not db.seen_unfinished_block(h_hash_1)
assert db.seen_unfinished_block(h_hash_1)
await db.clear_seen_unfinished_blocks()
assert not db.seen_unfinished_block(h_hash_1)
# Test adding non-connecting sub-slots genesis
assert store.get_sub_slot(test_constants.FIRST_CC_CHALLENGE) is None
assert store.get_sub_slot(sub_slots[0].challenge_chain.get_hash()) is None
assert store.get_sub_slot(sub_slots[1].challenge_chain.get_hash()) is None
assert not store.new_finished_sub_slot(sub_slots[1], {}, None)
assert not store.new_finished_sub_slot(sub_slots[2], {}, None)
except Exception:
await connection.close()
await connection_2.close()
await connection_3.close()
db_filename.unlink()
db_filename_2.unlink()
raise
# Test adding sub-slots after genesis
assert store.new_finished_sub_slot(sub_slots[0], {}, None)
assert store.get_sub_slot(sub_slots[0].challenge_chain.get_hash()) == sub_slots[0]
assert store.get_sub_slot(sub_slots[1].challenge_chain.get_hash()) is None
assert store.new_finished_sub_slot(sub_slots[1], {}, None)
for i in range(len(sub_slots)):
if i > 0:
assert store.new_finished_sub_slot(sub_slots[i], {}, None)
assert store.get_sub_slot(sub_slots[i].challenge_chain.get_hash()) == sub_slots[i]
# Different database should have different data
await FullNodeStore.create(connection_3)
assert store.get_finished_sub_slots(None, {}, sub_slots[-1].challenge_chain.get_hash(), False) == sub_slots
with raises(ValueError):
store.get_finished_sub_slots(None, {}, sub_slots[-1].challenge_chain.get_hash(), True)
await connection.close()
await connection_2.close()
await connection_3.close()
db_filename.unlink()
db_filename_2.unlink()
db_filename_3.unlink()
assert store.get_finished_sub_slots(None, {}, sub_slots[-2].challenge_chain.get_hash(), False) == sub_slots[:-1]
# Test adding genesis peak
await empty_blockchain.receive_block(blocks[0])
peak = empty_blockchain.get_peak()
if peak.overflow:
store.new_peak(peak, sub_slots[-1], sub_slots[-2], False, {})
else:
store.new_peak(peak, sub_slots[-1], None, False, {})
assert store.get_sub_slot(sub_slots[0].challenge_chain.get_hash()) is None
assert store.get_sub_slot(sub_slots[1].challenge_chain.get_hash()) is None
assert store.get_sub_slot(sub_slots[2].challenge_chain.get_hash()) is None
assert store.get_sub_slot(sub_slots[3].challenge_chain.get_hash()) == sub_slots[3]
assert store.get_sub_slot(sub_slots[4].challenge_chain.get_hash()) == sub_slots[4]
assert (
store.get_finished_sub_slots(
peak, empty_blockchain.sub_blocks, sub_slots[-1].challenge_chain.get_hash(), False
)
== []
)
# Test adding non genesis peak directly
blocks = bt.get_consecutive_blocks(test_constants, 2, skip_slots=2)
for block in blocks:
await empty_blockchain.receive_block(block)
sb = empty_blockchain.sub_blocks[block.header_hash]