More difficulty fixes and tests

This commit is contained in:
Mariano Sorgente 2020-11-23 23:50:09 +09:00 committed by Yostra
parent 3f064e529c
commit 069535e52e
11 changed files with 455 additions and 56 deletions

View File

@ -175,27 +175,30 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_ICC_EOS_VDF)
# 2g. Check infused challenge sub-slot hash in challenge sub-slot
if sub_slot.reward_chain.deficit == constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
# 2g. Check infused challenge sub-slot hash in challenge chain, deficit 5
if (
sub_slot.infused_challenge_chain.get_hash()
!= sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash
):
return None, ValidationError(Err.INVALID_ICC_HASH_CC)
else:
# 2h. Check infused challenge sub-slot hash not included for other deficits
if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None:
return None, ValidationError(Err.INVALID_ICC_HASH_CC)
# 2h. Check infused challenge sub-slot hash in reward sub-slot
# 2i. Check infused challenge sub-slot hash in reward sub-slot
if (
sub_slot.infused_challenge_chain.get_hash()
!= sub_slot.reward_chain.infused_challenge_chain_sub_slot_hash
):
return None, ValidationError(Err.INVALID_ICC_HASH_RC)
else:
assert sub_slot.infused_challenge_chain is None
# 2j. If no icc, check that the cc doesn't include it
if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None:
return None, ValidationError(Err.INVALID_ICC_HASH_CC)
# 2k. If no icc, check that the cc doesn't include it
if sub_slot.reward_chain.infused_challenge_chain_sub_slot_hash is not None:
return None, ValidationError(Err.INVALID_ICC_HASH_RC)
@ -203,31 +206,30 @@ async def validate_unfinished_header_block(
assert ses_hash is None # Only one of the slots can have it
ses_hash = sub_slot.challenge_chain.subepoch_summary_hash
# 2i. check sub-epoch summary hash is None for empty slots
# 2l. check sub-epoch summary hash is None for empty slots
if finished_sub_slot_n != 0:
if sub_slot.challenge_chain.subepoch_summary_hash is not None:
return None, ValidationError(Err.INVALID_SUB_EPOCH_SUMMARY_HASH)
# 2j. Check new difficulty
if finishes_epoch:
# 2m. Check new difficulty and ips
if sub_slot.challenge_chain.new_ips != ips:
return None, ValidationError(Err.INVALID_NEW_IPS)
if sub_slot.challenge_chain.new_difficulty != difficulty:
return None, ValidationError(Err.INVALID_NEW_DIFFICULTY)
else:
# 2n. Check new difficulty and ips are None if we don't finish epoch
if sub_slot.challenge_chain.new_ips is not None:
return None, ValidationError(Err.INVALID_NEW_IPS)
if sub_slot.challenge_chain.new_difficulty is not None:
return None, ValidationError(Err.INVALID_NEW_DIFFICULTY)
# 2k. Check challenge sub-slot hash in reward sub-slot
# 2o. Check challenge sub-slot hash in reward sub-slot
if sub_slot.challenge_chain.get_hash() != sub_slot.reward_chain.challenge_chain_sub_slot_hash:
return None, ValidationError(
Err.INVALID_CHALLENGE_SLOT_HASH_RC, "sub-slot hash in reward sub-slot mismatch"
)
# 2l. Check challenge chain sub-slot VDF
# 2m. Check end of reward slot VDF
sub_slot_iters = calculate_sub_slot_iters(constants, ips)
eos_vdf_iters: uint64 = sub_slot_iters
cc_start_element: ClassgroupElement = ClassgroupElement.get_default_element()
@ -256,6 +258,7 @@ async def validate_unfinished_header_block(
finished_sub_slot_n - 1
].reward_chain.get_hash()
# 2p. Check end of reward slot VDF
target_vdf_info = VDFInfo(
rc_eos_vdf_challenge,
ClassgroupElement.get_default_element(), # Reward chain always infuses at previous sub-block
@ -267,6 +270,7 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_RC_EOS_VDF)
# 2q. Check challenge chain sub-slot VDF
partial_cc_vdf_info = VDFInfo(
cc_eos_vdf_challenge,
cc_start_element,
@ -284,15 +288,15 @@ async def validate_unfinished_header_block(
if not sub_slot.proofs.challenge_chain_slot_proof.is_valid(constants, partial_cc_vdf_info, None):
return None, ValidationError(Err.INVALID_CC_EOS_VDF)
# 2n. Check deficit (5 deficit edge case for genesis block)
if genesis_block:
# 2r. Check deficit (5 deficit edge case for genesis block)
if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
return None, ValidationError(
Err.INVALID_DEFICIT, f"genesis, expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}"
)
else:
if prev_sb.deficit == 0:
# If there is a challenge chain infusion, resets deficit to 5
# 2s. If prev sb had deficit 0, resets deficit to 5
if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
log.error(
constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK,
@ -302,7 +306,7 @@ async def validate_unfinished_header_block(
f"expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}",
)
else:
# Otherwise, deficit stays the same at the slot ends, cannot reset until 0
# 2t. Otherwise, deficit stays the same at the slot ends, cannot reset until 0
if sub_slot.reward_chain.deficit != prev_sb.deficit:
return None, ValidationError(Err.INVALID_DEFICIT, "deficit is wrong at slot end")
@ -311,7 +315,7 @@ async def validate_unfinished_header_block(
if ses_hash is not None:
# 3a. Check that genesis block does not have sub-epoch summary
if genesis_block:
return None, ValidationError(Err.INVALID_SUB_EPOCH_SUMMARY, "genesis with sub-epoch-summary hash")
return None, ValidationError(Err.INVALID_SUB_EPOCH_SUMMARY_HASH, "genesis with sub-epoch-summary hash")
# 3b. Check that we finished a slot and we finished a sub-epoch
if not new_sub_slot or not finishes_se:

View File

@ -20,7 +20,6 @@ class ConsensusConstants:
SIGNIFICANT_BITS: int # The number of bits to look at in difficulty and min iters. The rest are zeroed
DISCRIMINANT_SIZE_BITS: int # Max is 1024 (based on ClassGroupElement int size)
NUMBER_ZERO_BITS_PLOT_FILTER: int # H(plot signature of the challenge) must start with these many zeroes
NUMBER_ZERO_BITS_SP_FILTER: int # H(plot signature of the sp) must start with these many zeroes
SLOT_TIME_TARGET: int # The target number of seconds per block
NUM_SP_INTERVALS_EXTRA: int
MAX_FUTURE_TIME: int # The next block can have a timestamp of at most these many seconds more

View File

@ -19,7 +19,6 @@ testnet_kwargs = {
"SIGNIFICANT_BITS": 12, # The number of bits to look at in difficulty and min iters. The rest are zeroed
"DISCRIMINANT_SIZE_BITS": 1024, # Max is 1024 (based on ClassGroupElement int size)
"NUMBER_ZERO_BITS_PLOT_FILTER": 3, # H(plot signature of the challenge) must start with these many zeroes
"NUMBER_ZERO_BITS_SP_FILTER": 4, # H(plot signature of the challenge) must start with these many zeroes
"SLOT_TIME_TARGET": 300, # The target number of seconds per slot
"NUM_SP_INTERVALS_EXTRA": 3, # The number of sp intervals to add to the signage point
"MAX_FUTURE_TIME": 7200, # The next block can have a timestamp of at most these many seconds more

View File

@ -29,12 +29,12 @@ def full_block_to_sub_block_record(
constants,
sub_blocks,
height_to_hash,
prev_sb.header_hash,
prev_sb.prev_hash,
prev_sb.height,
prev_sb.ips,
prev_sb.deficit,
len(block.finished_sub_slots) > 0,
prev_sb.total_iters,
prev_sb.sp_total_iters(constants),
)
overflow = is_overflow_sub_block(constants, block.reward_chain_sub_block.signage_point_index)
deficit = calculate_deficit(constants, block.height, prev_sb, overflow, len(block.finished_sub_slots) > 0)

View File

@ -1,7 +1,12 @@
from typing import Dict, Optional, Union
from src.consensus.constants import ConsensusConstants
from src.consensus.pot_iterations import calculate_ip_iters, calculate_sp_iters, is_overflow_sub_block
from src.consensus.pot_iterations import (
calculate_ip_iters,
calculate_sp_iters,
is_overflow_sub_block,
calculate_sub_slot_iters,
)
from src.consensus.deficit import calculate_deficit
from src.consensus.difficulty_adjustment import get_next_ips, finishes_sub_epoch, get_next_difficulty
from src.full_node.sub_block_record import SubBlockRecord
@ -60,11 +65,12 @@ def next_sub_epoch_summary(
ips = constants.IPS_STARTING
else:
assert prev_sb is not None
# This is the ips of the current block
ips = get_next_ips(
constants,
sub_blocks,
height_to_hash,
block.prev_header_hash,
prev_sb.prev_hash,
prev_sb.height,
prev_sb.ips,
prev_sb.deficit,
@ -85,28 +91,30 @@ def next_sub_epoch_summary(
if finishes_epoch:
sp_iters = calculate_sp_iters(constants, ips, signage_point_index)
ip_iters = calculate_ip_iters(constants, ips, signage_point_index, required_iters)
sub_slot_iters = calculate_sub_slot_iters(constants, ips)
next_difficulty = get_next_difficulty(
constants,
sub_blocks,
height_to_hash,
block.header_hash,
block.prev_header_hash,
block.height,
uint64(block.weight - prev_sb.weight),
deficit,
True,
uint128(block.total_iters - ip_iters + sp_iters),
uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
)
next_ips = get_next_ips(
constants,
sub_blocks,
height_to_hash,
block.header_hash,
block.prev_header_hash,
block.height,
ips,
deficit,
True,
uint128(block.total_iters - ip_iters + sp_iters),
uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
)
sp_total_iters = uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0))
else:
next_difficulty = None
next_ips = None

View File

@ -1,7 +1,8 @@
from typing import Dict, Optional, List
from src.types.full_block import FullBlock
from src.types.header import Header
from src.full_node.sub_block_record import SubBlockRecord
from src.types.sized_bytes import bytes32
from src.types.unfinished_header_block import UnfinishedHeaderBlock
from src.util.ints import uint32, uint64
from src.types.coin_record import CoinRecord
from src.rpc.rpc_client import RpcClient
@ -18,38 +19,35 @@ class FullNodeRpcClient(RpcClient):
async def get_blockchain_state(self) -> Dict:
response = await self.fetch("get_blockchain_state", {})
response["blockchain_state"]["tips"] = [
Header.from_json_dict(tip) for tip in response["blockchain_state"]["tips"]
]
response["blockchain_state"]["lca"] = Header.from_json_dict(response["blockchain_state"]["lca"])
response["blockchain_state"]["peak"] = SubBlockRecord.from_json_dict(response["blockchain_state"]["peak"])
return response["blockchain_state"]
async def get_block(self, header_hash) -> Optional[FullBlock]:
async def get_sub_block(self, header_hash) -> Optional[FullBlock]:
try:
response = await self.fetch("get_block", {"header_hash": header_hash.hex()})
response = await self.fetch("get_sub_block", {"header_hash": header_hash.hex()})
except Exception:
return None
return FullBlock.from_json_dict(response["sub_block"])
async def get_header_by_height(self, header_height) -> Optional[Header]:
async def get_sub_block_record_by_height(self, header_height) -> Optional[SubBlockRecord]:
try:
response = await self.fetch("get_header_by_height", {"height": header_height})
response = await self.fetch("get_sub_block_record_by_height", {"height": header_height})
except Exception:
return None
return Header.from_json_dict(response["header"])
return SubBlockRecord.from_json_dict(response["sub_block_record"])
async def get_header(self, header_hash) -> Optional[Header]:
async def get_sub_block_record(self, header_hash) -> Optional[SubBlockRecord]:
try:
response = await self.fetch("get_header", {"header_hash": header_hash.hex()})
if response["header"] is None:
response = await self.fetch("get_sub_block_record", {"header_hash": header_hash.hex()})
if response["sub_block_record"] is None:
return None
except Exception:
return None
return Header.from_json_dict(response["header"])
return SubBlockRecord.from_json_dict(response["sub_block_record"])
async def get_unfinished_block_headers(self, height: uint32) -> List[Header]:
response = await self.fetch("get_unfinished_block_headers", {"height": height})
return [Header.from_json_dict(r) for r in response["headers"]]
async def get_unfinished_sub_block_header_blocks(self, height: uint32) -> List[UnfinishedHeaderBlock]:
response = await self.fetch("get_unfinished_sub_block_header_blocks", {"height": height})
return [UnfinishedHeaderBlock.from_json_dict(r) for r in response["s"]]
async def get_network_space(self, newer_block_header_hash: str, older_block_header_hash: str) -> Optional[uint64]:
try:
@ -73,6 +71,6 @@ class FullNodeRpcClient(RpcClient):
CoinRecord.from_json_dict(coin) for coin in ((await self.fetch("get_unspent_coins", d))["coin_records"])
]
async def get_heaviest_block_seen(self) -> Header:
async def get_heaviest_block_seen(self) -> SubBlockRecord:
response = await self.fetch("get_heaviest_block_seen", {})
return Header.from_json_dict(response["tip"])
return SubBlockRecord.from_json_dict(response["peak"])

View File

@ -9,7 +9,6 @@ test_constants = constants.replace(
"MEMPOOL_BLOCK_BUFFER": 10,
"IPS_STARTING": 10 * 1,
"NUMBER_ZERO_BITS_PLOT_FILTER": 1, # H(plot signature of the challenge) must start with these many zeroes
"NUMBER_ZERO_BITS_SP_FILTER": 1, # H(plot signature of the challenge) must start with these many zeroes
"CLVM_COST_RATIO_CONSTANT": 108,
"COINBASE_FREEZE_PERIOD": 0,
}

View File

@ -103,7 +103,7 @@ class BlockTools:
mkdir(temp_dir)
args = Namespace()
# Can't go much lower than 18, since plots start having no solutions
args.size = 18
args.size = 19
# Uses many plots for testing, in order to guarantee proofs of space at every height
args.num = 320
args.buffer = 100
@ -202,7 +202,7 @@ class BlockTools:
) -> List[FullBlock]:
transaction_data_included = False
if time_per_sub_block is None:
time_per_sub_block = constants.SLOT_TIME_TARGET / constants.SLOT_SUB_BLOCKS_TARGET
time_per_sub_block: float = float(constants.SLOT_TIME_TARGET) / float(constants.SLOT_SUB_BLOCKS_TARGET)
if farmer_reward_puzzle_hash is None:
farmer_reward_puzzle_hash = self.farmer_ph
@ -848,14 +848,14 @@ def finish_sub_block(
slot_iters = calculate_sub_slot_iters(constants, ips)
if len(finished_sub_slots) == 0:
new_ip_iters = unfinished_block.total_iters - latest_sub_block.total_iters
if new_ip_iters < 0:
print("Here.")
cc_vdf_input = latest_sub_block.challenge_vdf_output
rc_vdf_challenge = latest_sub_block.reward_infusion_new_challenge
else:
new_ip_iters = ip_iters
cc_vdf_input = ClassgroupElement.get_default_element()
rc_vdf_challenge = slot_rc_challenge
if new_ip_iters < 0:
print("Bad here")
cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof(
constants,
cc_vdf_input,

View File

@ -13,6 +13,7 @@ from src.types.slots import InfusedChallengeChainSubSlot
from src.types.vdf import VDFInfo, VDFProof
from src.util.block_tools import get_vdf_info_and_proof
from src.util.errors import Err
from src.util.hash import std_hash
from src.util.ints import uint64, uint8, int512
from tests.recursive_replace import recursive_replace
from tests.setup_nodes import test_constants, bt
@ -94,9 +95,35 @@ class TestGenesisBlock:
class TestAddingMoreBlocks:
@pytest.mark.asyncio
async def test_basic_chain(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(test_constants, 400)
async def test_long_chain(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(test_constants, 200)
for block in blocks:
if (
len(block.finished_sub_slots) > 0
and block.finished_sub_slots[0].challenge_chain.subepoch_summary_hash is not None
):
# Sub/Epoch. Try using a bad ips and difficulty to test 2m and 2n
new_finished_ss = recursive_replace(
block.finished_sub_slots[0],
"challenge_chain.new_ips",
uint64(10000000),
)
block_bad = recursive_replace(
block, "finished_sub_slots", [new_finished_ss] + block.finished_sub_slots[1:]
)
result, err, _ = await empty_blockchain.receive_block(block_bad)
assert err == Err.INVALID_NEW_IPS
new_finished_ss_2 = recursive_replace(
block.finished_sub_slots[0],
"challenge_chain.new_difficulty",
uint64(10000000),
)
block_bad_2 = recursive_replace(
block, "finished_sub_slots", [new_finished_ss_2] + block.finished_sub_slots[1:]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_2)
assert err == Err.INVALID_NEW_DIFFICULTY
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
@ -301,8 +328,7 @@ class TestAddingMoreBlocks:
assert err == Err.SHOULD_NOT_HAVE_ICC
@pytest.mark.asyncio
async def test_invalid_icc_sub_slot_vdf_iters(self, empty_blockchain):
blockchain = empty_blockchain
async def test_invalid_icc_sub_slot_vdf(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(test_constants, 10)
for block in blocks:
if len(block.finished_sub_slots) > 0 and block.finished_sub_slots[-1].infused_challenge_chain is not None:
@ -395,13 +421,381 @@ class TestAddingMoreBlocks:
assert err == Err.INVALID_ICC_EOS_VDF
else:
result, err, _ = await blockchain.receive_block(block)
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
@pytest.mark.asyncio
async def test_invalid_icc_into_cc(self, empty_blockchain):
pass
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(test_constants, 1)
case_1, case_2 = False, False
while not case_1 or not case_2:
blocks = bt.get_consecutive_blocks(test_constants, 1, block_list=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 (
block.finished_sub_slots[-1].reward_chain.deficit
== test_constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK
):
# 2g
print("2g test")
case_1 = True
new_finished_ss = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
replace(
block.finished_sub_slots[-1].challenge_chain,
infused_challenge_chain_sub_slot_hash=bytes([1] * 32),
),
)
else:
# 2h
print("2h test")
case_2 = True
new_finished_ss = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
replace(
block.finished_sub_slots[-1].challenge_chain,
infused_challenge_chain_sub_slot_hash=block.finished_sub_slots[
-1
].infused_challenge_chain.get_hash(),
),
)
block_bad = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss]
)
result, err, _ = await blockchain.receive_block(block_bad)
assert err == Err.INVALID_ICC_HASH_CC
# 2i
new_finished_ss_bad_rc = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
replace(block.finished_sub_slots[-1].reward_chain, infused_challenge_chain_sub_slot_hash=None),
)
block_bad = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_bad_rc]
)
result, err, _ = await blockchain.receive_block(block_bad)
assert err == Err.INVALID_ICC_HASH_RC
elif len(block.finished_sub_slots) > 0 and block.finished_sub_slots[-1].infused_challenge_chain is None:
# 2j
new_finished_ss_bad_cc = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
replace(
block.finished_sub_slots[-1].challenge_chain,
infused_challenge_chain_sub_slot_hash=bytes([1] * 32),
),
)
block_bad = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_bad_cc]
)
print(len(block.finished_sub_slots))
result, err, _ = await blockchain.receive_block(block_bad)
assert err == Err.INVALID_ICC_HASH_CC
# 2k
new_finished_ss_bad_rc = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
replace(
block.finished_sub_slots[-1].reward_chain, infused_challenge_chain_sub_slot_hash=bytes([1] * 32)
),
)
block_bad = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_bad_rc]
)
result, err, _ = await blockchain.receive_block(block_bad)
assert err == Err.INVALID_ICC_HASH_RC
# Finally, add the block properly
result, err, _ = await blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
@pytest.mark.asyncio
async def test_empty_slot_no_ses(self, empty_blockchain):
# 2l
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(test_constants, 1)
blocks = bt.get_consecutive_blocks(test_constants, 1, block_list=blocks, skip_slots=4)
new_finished_ss = recursive_replace(
blocks[-1].finished_sub_slots[-1],
"challenge_chain",
replace(blocks[-1].finished_sub_slots[-1].challenge_chain, subepoch_summary_hash=std_hash(b"0")),
)
block_bad = recursive_replace(
blocks[-1], "finished_sub_slots", blocks[-1].finished_sub_slots[:-1] + [new_finished_ss]
)
result, err, _ = await blockchain.receive_block(block_bad)
assert err == Err.INVALID_SUB_EPOCH_SUMMARY_HASH
@pytest.mark.asyncio
async def test_wrong_cc_hash_rc(self, empty_blockchain):
# 2o
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(test_constants, 1, skip_slots=1)
blocks = bt.get_consecutive_blocks(test_constants, 1, skip_slots=1, block_list=blocks)
assert (await blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
new_finished_ss = recursive_replace(
blocks[-1].finished_sub_slots[-1],
"challenge_chain",
replace(blocks[-1].finished_sub_slots[-1].reward_chain, challenge_chain_sub_slot_hash=bytes([3] * 32)),
)
block_1_bad = recursive_replace(
blocks[-1], "finished_sub_slots", blocks[-1].finished_sub_slots[:-1] + [new_finished_ss]
)
result, err, _ = await blockchain.receive_block(block_1_bad)
assert result == ReceiveBlockResult.INVALID_BLOCK
assert err == Err.INVALID_CHALLENGE_SLOT_HASH_RC
@pytest.mark.asyncio
async def test_invalid_cc_sub_slot_vdf(self, empty_blockchain):
# 2q
blocks = bt.get_consecutive_blocks(test_constants, 10)
for block in blocks:
if len(block.finished_sub_slots):
# Bad iters
new_finished_ss = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
recursive_replace(
block.finished_sub_slots[-1].challenge_chain,
"challenge_chain_end_of_slot_vdf.number_of_iterations",
uint64(10000000),
),
)
block_bad = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss]
)
result, err, _ = await empty_blockchain.receive_block(block_bad)
assert err == Err.INVALID_CC_EOS_VDF
# Bad output
new_finished_ss_2 = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
recursive_replace(
block.finished_sub_slots[-1].challenge_chain,
"challenge_chain_end_of_slot_vdf.output",
ClassgroupElement.get_default_element(),
),
)
block_bad_2 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_2]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_2)
assert err == Err.INVALID_CC_EOS_VDF
# Bad challenge hash
new_finished_ss_3 = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
recursive_replace(
block.finished_sub_slots[-1].challenge_chain,
"challenge_chain_end_of_slot_vdf.challenge_hash",
bytes([1] * 32),
),
)
block_bad_3 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_3]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_3)
assert err == Err.INVALID_CC_EOS_VDF
# Bad input
new_finished_ss_4 = recursive_replace(
block.finished_sub_slots[-1],
"challenge_chain",
recursive_replace(
block.finished_sub_slots[-1].challenge_chain,
"challenge_chain_end_of_slot_vdf.input",
ClassgroupElement(int512(5), int512(1)),
),
)
block_bad_4 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_4]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_4)
assert err == Err.INVALID_CC_EOS_VDF
# Bad proof
new_finished_ss_5 = recursive_replace(
block.finished_sub_slots[-1],
"proofs.challenge_chain_slot_proof",
VDFProof(uint8(0), b"1239819023890"),
)
block_bad_5 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_5]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_5)
assert err == Err.INVALID_CC_EOS_VDF
else:
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
@pytest.mark.asyncio
async def test_invalid_rc_sub_slot_vdf(self, empty_blockchain):
# 2p
blocks = bt.get_consecutive_blocks(test_constants, 10)
for block in blocks:
if len(block.finished_sub_slots):
# Bad iters
new_finished_ss = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
recursive_replace(
block.finished_sub_slots[-1].reward_chain,
"reward_chain_end_of_slot_vdf.number_of_iterations",
uint64(10000000),
),
)
block_bad = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss]
)
result, err, _ = await empty_blockchain.receive_block(block_bad)
assert err == Err.INVALID_RC_EOS_VDF
# Bad output
new_finished_ss_2 = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
recursive_replace(
block.finished_sub_slots[-1].reward_chain,
"reward_chain_end_of_slot_vdf.output",
ClassgroupElement.get_default_element(),
),
)
block_bad_2 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_2]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_2)
assert err == Err.INVALID_RC_EOS_VDF
# Bad challenge hash
new_finished_ss_3 = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
recursive_replace(
block.finished_sub_slots[-1].reward_chain,
"end_of_slot_vdf.challenge_hash",
bytes([1] * 32),
),
)
block_bad_3 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_3]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_3)
assert err == Err.INVALID_RC_EOS_VDF
# Bad input
new_finished_ss_4 = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
recursive_replace(
block.finished_sub_slots[-1].reward_chain,
"end_of_slot_vdf.input",
ClassgroupElement(int512(5), int512(1)),
),
)
block_bad_4 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_4]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_4)
assert err == Err.INVALID_RC_EOS_VDF
# Bad proof
new_finished_ss_5 = recursive_replace(
block.finished_sub_slots[-1],
"proofs.reward_chain_slot_proof",
VDFProof(uint8(0), b"1239819023890"),
)
block_bad_5 = recursive_replace(
block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss_5]
)
result, err, _ = await empty_blockchain.receive_block(block_bad_5)
assert err == Err.INVALID_RC_EOS_VDF
else:
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
@pytest.mark.asyncio
async def test_genesis_bad_deficit(self, empty_blockchain):
# 2r
block = bt.get_consecutive_blocks(test_constants, 1, skip_slots=2)[0]
new_finished_ss = recursive_replace(
block.finished_sub_slots[-1],
"reward_chain",
recursive_replace(
block.finished_sub_slots[-1].reward_chain,
"deficit",
test_constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1,
),
)
block_bad = recursive_replace(block, "finished_sub_slots", block.finished_sub_slots[:-1] + [new_finished_ss])
result, err, _ = await empty_blockchain.receive_block(block_bad)
assert err == Err.INVALID_DEFICIT
@pytest.mark.asyncio
async def test_reset_deficit(self, empty_blockchain):
# 2s, 2t
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(test_constants, 2)
await empty_blockchain.receive_block(blocks[0])
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(test_constants, 1, block_list=blocks, skip_slots=1)
if len(blocks[-1].finished_sub_slots) > 0:
new_finished_ss = recursive_replace(
blocks[-1].finished_sub_slots[-1],
"reward_chain",
recursive_replace(
blocks[-1].finished_sub_slots[-1].reward_chain,
"deficit",
uint8(0),
),
)
if blockchain.sub_blocks[blocks[-2].header_hash].deficit == 0:
case_1 = True
else:
case_2 = True
block_bad = recursive_replace(
blocks[-1], "finished_sub_slots", blocks[-1].finished_sub_slots[:-1] + [new_finished_ss]
)
result, err, _ = await empty_blockchain.receive_block(block_bad)
assert err == Err.INVALID_DEFICIT or err == Err.INVALID_ICC_HASH_CC
result, err, _ = await empty_blockchain.receive_block(blocks[-1])
assert result == ReceiveBlockResult.NEW_PEAK
@pytest.mark.asyncio
async def test_genesis_has_ses(self, empty_blockchain):
# 3a
block = bt.get_consecutive_blocks(test_constants, 1, skip_slots=2)[0]
new_finished_ss = recursive_replace(
block.finished_sub_slots[0],
"reward_chain",
recursive_replace(
block.finished_sub_slots[0].challenge_chain,
"subepoch_summary_hash",
bytes([0] * 32),
),
)
block_bad = recursive_replace(block, "finished_sub_slots", [new_finished_ss] + block.finished_sub_slots[1:])
result, err, _ = await empty_blockchain.receive_block(block_bad)
assert err == Err.INVALID_SUB_EPOCH_SUMMARY_HASH
#

View File

@ -27,13 +27,12 @@ from tests.time_out_assert import time_out_assert
test_constants = constants.replace(
**{
"DIFFICULTY_STARTING": 2 ** 3,
"DIFFICULTY_STARTING": 2 ** 5,
"DISCRIMINANT_SIZE_BITS": 32,
"SUB_EPOCH_SUB_BLOCKS": 70,
"EPOCH_SUB_BLOCKS": 140,
"IPS_STARTING": 2 ** 8,
"IPS_STARTING": 2 ** 7,
"NUMBER_ZERO_BITS_PLOT_FILTER": 1, # H(plot signature of the challenge) must start with these many zeroes
"NUMBER_ZERO_BITS_SP_FILTER": 1, # H(plot signature of the challenge) must start with these many zeroes
"MAX_FUTURE_TIME": 3600
* 24
* 10, # Allows creating blockchains with timestamps up to 10 days in the future, for testing

View File

@ -13,7 +13,6 @@ test_constants = constants.replace(
"DIFFICULTY_STARTING": 1000,
"MIN_ITERS_STARTING": 100000,
"NUMBER_ZERO_BITS_PLOT_FILTER": 1,
"NUMBER_ZERO_BITS_SP_FILTER": 1,
}
)