Many timelord and full node fixes to difficulty and sub epochs

This commit is contained in:
Mariano Sorgente 2020-12-08 15:36:58 +09:00 committed by Yostra
parent 2c4eb44cea
commit 075498a044
10 changed files with 245 additions and 118 deletions

View File

@ -79,9 +79,12 @@ async def validate_unfinished_header_block(
difficulty = constants.DIFFICULTY_STARTING
else:
height: uint32 = uint32(prev_sb.sub_block_height + 1)
can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch(
constants, prev_sb.sub_block_height, prev_sb.deficit, sub_blocks, prev_sb.prev_hash
)
if prev_sb.sub_epoch_summary_included is not None:
can_finish_se, can_finish_epoch = False, False
else:
can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch(
constants, prev_sb.sub_block_height, prev_sb.deficit, sub_blocks, prev_sb.prev_hash, False
)
can_finish_se = can_finish_se and new_sub_slot
can_finish_epoch = can_finish_epoch and new_sub_slot
@ -103,7 +106,6 @@ async def validate_unfinished_header_block(
if genesis_block:
# 2a. check sub-slot challenge hash for genesis block
if challenge_hash != constants.FIRST_CC_CHALLENGE:
log.error(f"1 {challenge_hash} {constants.FIRST_CC_CHALLENGE}")
return None, ValidationError(Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
else:
curr: SubBlockRecord = prev_sb
@ -112,7 +114,6 @@ async def validate_unfinished_header_block(
# 2b. check sub-slot challenge hash for non-genesis block
if not curr.finished_challenge_slot_hashes[-1] == challenge_hash:
log.error(f"2 {challenge_hash} {curr.finished_challenge_slot_hashes[-1]}")
return None, ValidationError(Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
else:
# 2c. check sub-slot challenge hash for empty slot
@ -120,9 +121,6 @@ async def validate_unfinished_header_block(
not header_block.finished_sub_slots[finished_sub_slot_n - 1].challenge_chain.get_hash()
== challenge_hash
):
log.error(
f"3 {challenge_hash} {header_block.finished_sub_slots[finished_sub_slot_n - 1].challenge_chain.get_hash()}"
)
return None, ValidationError(Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
if genesis_block:
@ -366,6 +364,7 @@ async def validate_unfinished_header_block(
)
expected_hash = expected_sub_epoch_summary.get_hash()
if expected_hash != ses_hash:
log.error(f"{expected_sub_epoch_summary}")
return None, ValidationError(
Err.INVALID_SUB_EPOCH_SUMMARY, f"expected ses hash: {expected_hash} got {ses_hash} "
)

View File

@ -3,22 +3,22 @@ from .constants import ConsensusConstants
testnet_kwargs = {
# TODO(mariano): write comments here
"SLOT_SUB_BLOCKS_TARGET": 32,
"MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK": 16,
"MAX_SUB_SLOT_SUB_BLOCKS": 128,
"NUM_SPS_SUB_SLOT": 8,
"SLOT_SUB_BLOCKS_TARGET": 7,
"MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK": 3,
"MAX_SUB_SLOT_SUB_BLOCKS": 12,
"NUM_SPS_SUB_SLOT": 16,
"SUB_SLOT_ITERS_STARTING": 2 ** 22,
# DIFFICULTY_STARTING is the starting difficulty for the first epoch, which is then further
# multiplied by another factor of 2^25, to be used in the VDF iter calculation formula.
"DIFFICULTY_STARTING": 2 ** 14,
"DIFFICULTY_FACTOR": 3, # The next difficulty is truncated to range [prev / FACTOR, prev * FACTOR]
# These 3 constants must be changed at the same time
"SUB_EPOCH_SUB_BLOCKS": 128, # The number of sub-blocks per sub-epoch, mainnet 284
"EPOCH_SUB_BLOCKS": 256, # The number of sub-blocks per epoch, mainnet 32256
"SUB_EPOCH_SUB_BLOCKS": 32, # The number of sub-blocks per sub-epoch, mainnet 284
"EPOCH_SUB_BLOCKS": 64, # The number of sub-blocks per epoch, mainnet 32256
"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": 9, # H(plot signature of the challenge) must start with these many zeroes
"SUB_SLOT_TIME_TARGET": 600, # The target number of seconds per slot, mainnet 600
"SUB_SLOT_TIME_TARGET": 128, # The target number of seconds per slot, mainnet 600
"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
"NUMBER_OF_TIMESTAMPS": 11, # Than the average of the last NUMBER_OF_TIMESTAMPS blocks

View File

@ -56,7 +56,6 @@ def _get_blocks_at_height(
def _get_last_block_in_previous_epoch(
constants: ConsensusConstants,
next_sub_height: uint32,
sub_height_to_hash: Dict[uint32, bytes32],
sub_blocks: Dict[bytes32, SubBlockRecord],
prev_sb: SubBlockRecord,
@ -67,7 +66,6 @@ def _get_last_block_in_previous_epoch(
Args:
constants: consensus constants being used for this chain
next_sub_height: sub-block height of the first sub-block in the new epoch
sub_height_to_hash: sub-block height to header hash map for sub-blocks in peak path
sub_blocks: dict from header hash to sub-block of all relevant sub-blocks
prev_sb: last-sub-block in the current epoch.
@ -81,10 +79,13 @@ def _get_last_block_in_previous_epoch(
before the final sub-block in the epoch. Block at height 0 is an exception.
# TODO: check edge cases here
"""
height_epoch_surpass: uint32 = next_sub_height - (next_sub_height % constants.EPOCH_SUB_BLOCKS)
height_in_next_epoch = prev_sb.sub_block_height + constants.MAX_SUB_SLOT_SUB_BLOCKS + 3
height_epoch_surpass: uint32 = uint32(height_in_next_epoch - (height_in_next_epoch % constants.EPOCH_SUB_BLOCKS))
height_prev_epoch_surpass: uint32 = height_epoch_surpass - constants.EPOCH_SUB_BLOCKS
if (next_sub_height - height_epoch_surpass) > constants.MAX_SUB_SLOT_SUB_BLOCKS:
raise ValueError(f"Height at {next_sub_height} should not create a new slot, it is far past the epoch barrier")
if (height_in_next_epoch - height_epoch_surpass) > (3 * constants.MAX_SUB_SLOT_SUB_BLOCKS):
raise ValueError(
f"Height at {prev_sb.sub_block_height + 1} should not create a new epoch, it is far past the epoch barrier"
)
height_prev_epoch_surpass: uint32 = height_epoch_surpass - constants.EPOCH_SUB_BLOCKS
if height_prev_epoch_surpass == 0:
@ -134,12 +135,16 @@ def can_finish_sub_and_full_epoch(
deficit: uint8,
sub_blocks: Dict[bytes32, SubBlockRecord],
prev_header_hash: Optional[bytes32],
can_finish_soon: bool = False,
) -> (bool, bool):
"""
Returns a bool tuple
first bool is true if the next sub-slot after sub_block_height will form part of a new sub-epoch.
first bool is true if the next sub-slot after sub_block_height will form part of a new sub-epoch. Therefore
sub_block height is the last sub-block, and sub_block_height + 1 is in a new sub-epoch.
second bool is true if the next sub-slot after sub_block_height will form part of a new sub-epoch and epoch.
Warning: This assumes the previous sub-block did not finish a sub-epoch. TODO: check
Therefore, sub_block height is the last sub-block, and sub_block_height + 1 is in a new epoch.
Warning: This assumes the previous sub-block is not the last sub-block in the sub-epoch (which means this
current block does not include a sub epoch summary). TODO: check, simplify, and test code
Args:
constants: consensus constants being used for this chain
@ -147,24 +152,43 @@ def can_finish_sub_and_full_epoch(
deficit: deficit of the sub-block at sub_block_height
sub_blocks: dictionary from header hash to SBR of all included SBR
prev_header_hash: prev_header hash of the sub-block at sub_block_height, assuming not genesis
can_finish_soon: this is useful when sending SES to timelords. We might not be able to finish it, but we will
soon (within MAX_SUB_SLOT_SUB_BLOCKS)
"""
if sub_block_height < constants.SUB_EPOCH_SUB_BLOCKS - 1:
if sub_block_height < constants.SUB_EPOCH_SUB_BLOCKS - constants.MAX_SUB_SLOT_SUB_BLOCKS - 1:
return False, False
# Used along with "can_finish_soon"
future_sb_height = sub_block_height + constants.MAX_SUB_SLOT_SUB_BLOCKS + 1
assert prev_header_hash is not None
# If last slot does not have enough blocks for a new challenge chain infusion, return same difficulty
if deficit > 0:
return False, False
if not can_finish_soon:
if deficit > 0:
return False, False
# Disqualify blocks which are too far past in height
# The maximum possible height which includes sub epoch summary
if (sub_block_height + 1) % constants.SUB_EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS:
return False, False
# Disqualify blocks which are too far past in height
# The maximum possible height which includes sub epoch summary
if (sub_block_height + 1) % constants.SUB_EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS:
return False, False
check_already_included = (sub_block_height + 1) % constants.SUB_EPOCH_SUB_BLOCKS > 1
else:
# If can_finish_soon=True, we still want to make sure that we will be finishing a sub-epoch soon.
# Here we check if a theoretical future block can finish the sub-epoch
if (
(sub_block_height + 1) % constants.SUB_EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS
and future_sb_height % constants.SUB_EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS
):
return False, False
# Don't check already included if we are not at the sub-epoch barrier yet.
check_already_included = (
1 < (sub_block_height + 1) % constants.SUB_EPOCH_SUB_BLOCKS <= constants.MAX_SUB_SLOT_SUB_BLOCKS
)
# For sub-blocks which equal 0 or 1, we assume that the sub-epoch has not been finished yet
if (sub_block_height + 1) % constants.SUB_EPOCH_SUB_BLOCKS > 1:
if check_already_included:
already_included_ses = False
curr: SubBlockRecord = sub_blocks[prev_header_hash]
while curr.sub_block_height % constants.SUB_EPOCH_SUB_BLOCKS > 0:
@ -177,8 +201,15 @@ def can_finish_sub_and_full_epoch(
return False, False
# For checking new epoch, make sure the epoch sub blocks are aligned
if (sub_block_height + 1) % constants.EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS:
return True, False
if not can_finish_soon:
if (sub_block_height + 1) % constants.EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS:
return True, False
else:
if (
(sub_block_height + 1) % constants.EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS
and future_sb_height % constants.EPOCH_SUB_BLOCKS > constants.MAX_SUB_SLOT_SUB_BLOCKS
):
return True, False
return True, True
@ -193,6 +224,7 @@ def get_next_sub_slot_iters(
deficit: uint8,
new_slot: bool,
signage_point_total_iters: uint128,
skip_epoch_check=False,
) -> uint64:
"""
Returns the slot iterations required for the next block after the one at sub_block_height, where new_slot is true
@ -208,10 +240,11 @@ def get_next_sub_slot_iters(
deficit: deficit of the sub_block at sub_block_height
new_slot: whether or not there is a new slot after sub_block_height
signage_point_total_iters: signage point iters of the sub_block at sub_block_height
skip_epoch_check: don't check correct epoch
"""
next_sub_block_height: uint32 = uint32(sub_block_height + 1)
if next_sub_block_height < constants.EPOCH_SUB_BLOCKS:
if next_sub_block_height < (constants.EPOCH_SUB_BLOCKS - constants.MAX_SUB_SLOT_SUB_BLOCKS):
return uint64(constants.SUB_SLOT_ITERS_STARTING)
if prev_header_hash not in sub_blocks:
@ -220,15 +253,14 @@ def get_next_sub_slot_iters(
prev_sb: SubBlockRecord = sub_blocks[prev_header_hash]
# If we are in the same epoch, return same ssi
_, can_finish_epoch = can_finish_sub_and_full_epoch(
constants, sub_block_height, deficit, sub_blocks, prev_header_hash
)
if not new_slot or not can_finish_epoch:
return curr_sub_slot_iters
if not skip_epoch_check:
_, can_finish_epoch = can_finish_sub_and_full_epoch(
constants, sub_block_height, deficit, sub_blocks, prev_header_hash, False
)
if not new_slot or not can_finish_epoch:
return curr_sub_slot_iters
last_block_prev: SubBlockRecord = _get_last_block_in_previous_epoch(
constants, next_sub_block_height, height_to_hash, sub_blocks, prev_sb
)
last_block_prev: SubBlockRecord = _get_last_block_in_previous_epoch(constants, height_to_hash, sub_blocks, prev_sb)
# Ensure we get a block for the last block as well, and that it is before the signage point
last_block_curr = prev_sb
@ -276,6 +308,7 @@ def get_next_difficulty(
deficit: uint8,
new_slot: bool,
signage_point_total_iters: uint128,
skip_epoch_check=False,
) -> uint64:
"""
Returns the difficulty of the next sub-block that extends onto sub-block.
@ -292,10 +325,11 @@ def get_next_difficulty(
deficit: deficit of the sub_block at sub_block_height
new_slot: whether or not there is a new slot after sub_block_height
signage_point_total_iters: signage point iters of the sub_block at sub_block_height
skip_epoch_check: don't check correct epoch
"""
next_sub_block_height: uint32 = uint32(sub_block_height + 1)
if next_sub_block_height < constants.EPOCH_SUB_BLOCKS:
if next_sub_block_height < (constants.EPOCH_SUB_BLOCKS - constants.MAX_SUB_SLOT_SUB_BLOCKS):
# We are in the first epoch
return uint64(constants.DIFFICULTY_STARTING)
@ -305,15 +339,14 @@ def get_next_difficulty(
prev_sb: SubBlockRecord = sub_blocks[prev_header_hash]
# If we are in the same slot as previous sub-block, return same difficulty
_, can_finish_epoch = can_finish_sub_and_full_epoch(
constants, sub_block_height, deficit, sub_blocks, prev_header_hash
)
if not new_slot or not can_finish_epoch:
return current_difficulty
if not skip_epoch_check:
_, can_finish_epoch = can_finish_sub_and_full_epoch(
constants, sub_block_height, deficit, sub_blocks, prev_header_hash, False
)
if not new_slot or not can_finish_epoch:
return current_difficulty
last_block_prev: SubBlockRecord = _get_last_block_in_previous_epoch(
constants, next_sub_block_height, height_to_hash, sub_blocks, prev_sb
)
last_block_prev: SubBlockRecord = _get_last_block_in_previous_epoch(constants, height_to_hash, sub_blocks, prev_sb)
# Ensure we get a block for the last block as well, and that it is before the signage point
last_block_curr = prev_sb

View File

@ -19,6 +19,10 @@ from src.types.sub_epoch_summary import SubEpochSummary
from src.types.unfinished_block import UnfinishedBlock
from src.util.ints import uint32, uint64, uint8, uint128
import logging
log = logging.getLogger(__name__)
def make_sub_epoch_summary(
constants: ConsensusConstants,
@ -42,14 +46,13 @@ def make_sub_epoch_summary(
new_sub_slot_iters: sub slot iters in new epoch
"""
assert prev_prev_sub_block.sub_block_height == blocks_included_height - 2
# If first sub_epoch
if blocks_included_height // constants.SUB_EPOCH_SUB_BLOCKS == 1:
# If first sub_epoch. Adds MAX_SUB_SLOT_SUB_BLOCKS because blocks_included_height might be behind
if (blocks_included_height + constants.MAX_SUB_SLOT_SUB_BLOCKS) // constants.SUB_EPOCH_SUB_BLOCKS == 1:
return SubEpochSummary(constants.GENESIS_SES_HASH, constants.FIRST_RC_CHALLENGE, uint8(0), None, None)
curr: SubBlockRecord = prev_prev_sub_block
while curr.sub_epoch_summary_included is None:
curr = sub_blocks[curr.prev_hash]
assert curr.sub_epoch_summary_included is not None
curr = sub_blocks.get(curr.prev_hash, None)
assert curr is not None
prev_ses = curr.sub_epoch_summary_included.get_hash()
return SubEpochSummary(
prev_ses,
@ -66,6 +69,7 @@ def next_sub_epoch_summary(
height_to_hash: Dict[uint32, bytes32],
required_iters: uint64,
block: Union[UnfinishedBlock, FullBlock],
can_finish_soon: bool = False,
) -> Optional[SubEpochSummary]:
"""
Returns the sub-epoch summary that can be included in the sub-block after block. If it should include one. Block
@ -78,6 +82,8 @@ def next_sub_epoch_summary(
height_to_hash: dictionary from sub-block height to header hash
required_iters: required iters of the proof of space in block
block: the (potentially) last sub-block in the new epoch
can_finish_soon: this is useful when sending SES to timelords. We might not be able to finish it, but we will
soon (within MAX_SUB_SLOT_SUB_BLOCKS)
Returns:
object: the new sub-epoch summary
@ -87,6 +93,10 @@ def next_sub_epoch_summary(
if prev_sb is None or prev_sb.sub_block_height == 0:
return None
if len(block.finished_sub_slots) > 0 and block.finished_sub_slots[0].challenge_chain.new_difficulty is not None:
# We just included a sub-epoch summary
return None
assert prev_sb is not None
# This is the ssi of the current block
sub_slot_iters = get_next_sub_slot_iters(
@ -110,6 +120,7 @@ def next_sub_epoch_summary(
deficit,
sub_blocks,
prev_sb.header_hash if prev_sb is not None else None,
can_finish_soon,
)
# can't finish se, no summary
@ -133,6 +144,7 @@ def next_sub_epoch_summary(
deficit,
True,
uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
True,
)
next_sub_slot_iters = get_next_sub_slot_iters(
constants,
@ -144,6 +156,7 @@ def next_sub_epoch_summary(
deficit,
True,
uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
True,
)
return make_sub_epoch_summary(

View File

@ -20,22 +20,21 @@ class BlockStore:
# All full blocks which have been added to the blockchain. Header_hash -> block
self.db = connection
await self.db.execute(
"CREATE TABLE IF NOT EXISTS full_blocks(header_hash text PRIMARY KEY, sub_height bigint, height bigint, is_block tinyint, block blob)"
f"CREATE TABLE IF NOT EXISTS full_blocks(header_hash text PRIMARY KEY, sub_height bigint, is_block tinyint, "
f"block blob)"
)
# Sub block records
await self.db.execute(
"CREATE TABLE IF NOT EXISTS sub_block_records(header_hash "
"text PRIMARY KEY, prev_hash text, sub_height bigint, height bigint, weight bigint, total_iters text,"
"text PRIMARY KEY, prev_hash text, sub_height bigint,"
"sub_block blob, is_peak tinyint, is_block tinyint)"
)
# Height index so we can look up in order of height for sync purposes
await self.db.execute("CREATE INDEX IF NOT EXISTS full_block_sub_height on full_blocks(sub_height)")
await self.db.execute("CREATE INDEX IF NOT EXISTS full_block_height on full_blocks(height)")
await self.db.execute("CREATE INDEX IF NOT EXISTS is_block on full_blocks(is_block)")
await self.db.execute("CREATE INDEX IF NOT EXISTS sub_block_height on sub_block_records(height)")
await self.db.execute("CREATE INDEX IF NOT EXISTS sub_block_sub_height on sub_block_records(sub_height)")
await self.db.execute("CREATE INDEX IF NOT EXISTS hh on sub_block_records(header_hash)")
@ -47,23 +46,23 @@ class BlockStore:
return self
async def add_full_block(self, block: FullBlock, sub_block: SubBlockRecord) -> None:
if block.is_block():
height = block.height
else:
height = sub_block.prev_block_height + 1
cursor_1 = await self.db.execute("INSERT OR REPLACE INTO full_blocks VALUES(?, ?, ?, ?, ?)",
(block.header_hash.hex(), block.sub_block_height, height, int(block.is_block()), bytes(block)),
)
cursor_1 = await self.db.execute(
"INSERT OR REPLACE INTO full_blocks VALUES(?, ?, ?, ?)",
(block.header_hash.hex(), block.sub_block_height, int(block.is_block()), bytes(block)),
)
await cursor_1.close()
# proof_hash = std_hash(block.proof_of_space.get_hash() + block.proof_of_time.output.get_hash())
weight = block.weight.to_bytes(128 // 8, "big", signed=False).hex()
total_iters = block.total_iters.to_bytes(128 // 8, "big", signed=False).hex()
cursor_2 = await self.db.execute(f"INSERT OR REPLACE INTO sub_block_records VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
(block.header_hash.hex(), block.prev_header_hash.hex(), block.sub_block_height, height,
weight, total_iters, bytes(sub_block), False, block.is_block())
cursor_2 = await self.db.execute(
f"INSERT OR REPLACE INTO sub_block_records VALUES(?, ?, ?, ?, ?, ?)",
(
block.header_hash.hex(),
block.prev_header_hash.hex(),
block.sub_block_height,
bytes(sub_block),
False,
block.is_block(),
),
)
await cursor_2.close()
await self.db.commit()
@ -111,8 +110,8 @@ class BlockStore:
peak: Optional[bytes32] = None
for row in rows:
header_hash = bytes.fromhex(row[0])
ret[header_hash] = SubBlockRecord.from_bytes(row[6])
if row[7]:
ret[header_hash] = SubBlockRecord.from_bytes(row[3])
if row[4]:
assert peak is None # Sanity check, only one peak
peak = header_hash
return ret, peak

View File

@ -11,7 +11,7 @@ import aiosqlite
import src.server.ws_connection as ws
from src.consensus.blockchain import Blockchain, ReceiveBlockResult
from src.consensus.constants import ConsensusConstants
from src.consensus.difficulty_adjustment import get_sub_slot_iters_and_difficulty
from src.consensus.difficulty_adjustment import get_sub_slot_iters_and_difficulty, can_finish_sub_and_full_epoch
from src.consensus.make_sub_epoch_summary import next_sub_epoch_summary
from src.consensus.pot_iterations import is_overflow_sub_block
from src.consensus.sub_block_record import SubBlockRecord
@ -178,6 +178,7 @@ class FullNode:
self.blockchain.sub_height_to_hash,
peak.required_iters,
peak_block,
True,
)
recent_rc = self.blockchain.get_recent_reward_challenges()
@ -189,7 +190,6 @@ class FullNode:
last_csb_or_eos = curr.total_iters
else:
last_csb_or_eos = curr.ip_sub_slot_total_iters(self.constants)
timelord_new_peak: timelord_protocol.NewPeak = timelord_protocol.NewPeak(
peak_block.reward_chain_sub_block,
difficulty,
@ -633,6 +633,54 @@ class FullNode:
# This means this unfinished block is pretty far behind, it will not add weight to our chain
return
prev_sb = (
None
if block.prev_header_hash == self.constants.GENESIS_PREV_HASH
else self.blockchain.sub_blocks[block.prev_header_hash]
)
is_overflow = is_overflow_sub_block(self.constants, block.reward_chain_sub_block.signage_point_index)
# Count the sub-blocks in sub slot, and check if it's a new epoch
first_ss_new_epoch = False
if len(block.finished_sub_slots) > 0:
num_sub_blocks_in_ss = 1 # Curr
if block.finished_sub_slots[-1].challenge_chain.new_difficulty is not None:
first_ss_new_epoch = True
else:
curr = self.blockchain.sub_blocks.get(block.prev_header_hash, None)
num_sub_blocks_in_ss = 2 # Curr and prev
while (curr is not None) and not curr.first_in_sub_slot:
curr = self.blockchain.sub_blocks.get(curr.prev_hash, None)
num_sub_blocks_in_ss += 1
if (
curr is not None
and curr.first_in_sub_slot
and curr.sub_epoch_summary_included is not None
and curr.sub_epoch_summary_included.new_difficulty is not None
):
first_ss_new_epoch = True
elif prev_sb is not None:
# If the prev can finish an epoch, then we are in a new epoch
prev_prev = self.blockchain.sub_blocks.get(prev_sb.prev_hash, None)
_, can_finish_epoch = can_finish_sub_and_full_epoch(
self.constants,
prev_sb.sub_block_height,
prev_sb.deficit,
self.blockchain.sub_blocks,
prev_sb.header_hash if prev_prev is not None else None,
False,
)
if can_finish_epoch:
first_ss_new_epoch = True
if is_overflow and first_ss_new_epoch:
# No overflow sub-blocks in new epoch
return
if num_sub_blocks_in_ss > self.constants.MAX_SUB_SLOT_SUB_BLOCKS:
self.log.warning("Too many sub-blocks added, not adding sub-block")
return
async with self.blockchain.lock:
# TODO: pre-validate VDFs outside of lock
required_iters, error_code = await self.blockchain.validate_unfinished_block(block)
@ -651,25 +699,12 @@ class FullNode:
sub_height = self.blockchain.sub_blocks[block.prev_header_hash].sub_block_height + 1
ses: Optional[SubEpochSummary] = next_sub_epoch_summary(
self.constants,
self.blockchain.sub_blocks,
self.blockchain.sub_height_to_hash,
required_iters,
block,
self.constants, self.blockchain.sub_blocks, self.blockchain.sub_height_to_hash, required_iters, block, True
)
is_overflow = is_overflow_sub_block(self.constants, block.reward_chain_sub_block.signage_point_index)
if ses is not None and ses.new_difficulty is not None and is_overflow:
# No overflow sub-blocks in new epoch
return
self.full_node_store.add_unfinished_block(sub_height, block)
self.log.info(f"Added unfinished_block {block.partial_hash}")
prev_sb = (
None
if block.prev_header_hash == self.constants.GENESIS_PREV_HASH
else self.blockchain.sub_blocks[block.prev_header_hash]
)
sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty(
self.constants,
block,

View File

@ -350,7 +350,8 @@ class FullNodeAPI:
if added:
self.log.warning(
f"Finished signage point {request.index_from_challenge}/{self.full_node.constants.NUM_SPS_SUB_SLOT}: {request.challenge_chain_vdf.output.get_hash()}"
f"Finished signage point {request.index_from_challenge}/{self.full_node.constants.NUM_SPS_SUB_SLOT}: "
f"{request.challenge_chain_vdf.output.get_hash()} "
)
sub_slot_tuple = self.full_node.full_node_store.get_sub_slot(request.challenge_chain_vdf.challenge)
if sub_slot_tuple is not None:

View File

@ -79,8 +79,15 @@ class IterationType(Enum):
END_OF_SUBSLOT = 3
class StateType(Enum):
PEAK = 1
END_OF_SUB_SLOT = 2
FIRST_SUB_SLOT = 3
class LastState:
def __init__(self, constants: ConsensusConstants):
self.state_type: StateType = StateType.FIRST_SUB_SLOT
self.peak: Optional[timelord_protocol.NewPeak] = None
self.subslot_end: Optional[EndOfSubSlotBundle] = None
self.last_ip: uint64 = uint64(0)
@ -93,14 +100,13 @@ class LastState:
self.last_challenge_sb_or_eos_total_iters = uint128(0)
self.last_block_total_iters: Optional[uint128] = None
self.last_peak_challenge: bytes32 = constants.FIRST_RC_CHALLENGE
self.first_sub_slot_no_peak: bool = True
self.difficulty: uint64 = constants.DIFFICULTY_STARTING
self.sub_slot_iters: uint64 = constants.SUB_SLOT_ITERS_STARTING
self.reward_challenge_cache: List[Tuple[bytes32, uint128]] = [(constants.FIRST_RC_CHALLENGE, uint128(0))]
def set_state(self, state: Union[timelord_protocol.NewPeak, EndOfSubSlotBundle]):
self.first_sub_slot_no_peak = False
if isinstance(state, timelord_protocol.NewPeak):
self.state_type = StateType.PEAK
self.peak = state
self.subslot_end = None
_, self.last_ip = iters_from_sub_block(
@ -123,6 +129,7 @@ class LastState:
self.last_challenge_sb_or_eos_total_iters = self.peak.last_challenge_sb_or_eos_total_iters
if isinstance(state, EndOfSubSlotBundle):
self.state_type = StateType.END_OF_SUB_SLOT
if self.peak is not None:
self.total_iters = self.total_iters - self.get_last_ip() + self.sub_slot_iters
else:
@ -144,6 +151,18 @@ class LastState:
def get_sub_slot_iters(self) -> uint64:
return self.sub_slot_iters
def can_infuse_sub_block(self) -> bool:
if self.state_type == StateType.FIRST_SUB_SLOT or self.state_type == StateType.END_OF_SUB_SLOT:
return True
ss_start_iters = self.get_total_iters() - self.get_last_ip()
already_infused_count: int = 0
for _, total_iters in self.reward_challenge_cache:
if total_iters > ss_start_iters:
already_infused_count += 1
if already_infused_count >= self.constants.MAX_SUB_SLOT_SUB_BLOCKS:
return False
return True
def get_weight(self) -> uint128:
return self.last_weight
@ -165,21 +184,37 @@ class LastState:
def get_deficit(self) -> uint8:
return self.deficit
def get_sub_epoch_summary(self) -> Optional[SubEpochSummary]:
def get_infused_sub_epoch_summary(self) -> Optional[SubEpochSummary]:
if self.state_type == StateType.FIRST_SUB_SLOT or self.state_type == StateType.PEAK:
# Only sub slots can infuse SES
return None
return self.sub_epoch_summary
def get_next_sub_epoch_summary(self) -> Optional[SubEpochSummary]:
if self.state_type == StateType.FIRST_SUB_SLOT or self.state_type == StateType.END_OF_SUB_SLOT:
# Can only infuse SES after a peak (in an end of sub slot)
return None
assert self.peak is not None
if (
self.peak.reward_chain_sub_block.sub_block_height + 1
) % self.constants.SUB_EPOCH_SUB_BLOCKS <= self.constants.MAX_SUB_SLOT_SUB_BLOCKS and (self.get_deficit() == 0):
return self.sub_epoch_summary
return None
def get_last_block_total_iters(self) -> Optional[uint128]:
return self.last_block_total_iters
def get_challenge(self, chain: Chain) -> Optional[bytes32]:
if self.first_sub_slot_no_peak:
if self.state_type == StateType.FIRST_SUB_SLOT:
assert self.peak is None and self.subslot_end is None
if chain == Chain.CHALLENGE_CHAIN:
return self.constants.FIRST_CC_CHALLENGE
elif chain == Chain.REWARD_CHAIN:
return self.constants.FIRST_RC_CHALLENGE
elif chain == Chain.INFUSED_CHALLENGE_CHAIN:
return None
elif self.peak is not None:
elif self.state_type == StateType.PEAK:
assert self.peak is not None
sub_block = self.peak.reward_chain_sub_block
if chain == Chain.CHALLENGE_CHAIN:
return sub_block.challenge_chain_ip_vdf.challenge
@ -196,7 +231,8 @@ class LastState:
sub_block.challenge_chain_ip_vdf,
).get_hash()
return None
elif self.subslot_end is not None:
elif self.state_type == StateType.END_OF_SUB_SLOT:
assert self.subslot_end is not None
if chain == Chain.CHALLENGE_CHAIN:
return self.subslot_end.challenge_chain.get_hash()
elif chain == Chain.REWARD_CHAIN:
@ -208,9 +244,9 @@ class LastState:
return None
def get_initial_form(self, chain: Chain) -> Optional[ClassgroupElement]:
if self.first_sub_slot_no_peak:
if self.state_type == StateType.FIRST_SUB_SLOT:
return ClassgroupElement.get_default_element()
if self.peak is not None:
elif self.state_type == StateType.PEAK:
sub_block = self.peak.reward_chain_sub_block
if chain == Chain.CHALLENGE_CHAIN:
return sub_block.challenge_chain_ip_vdf.output
@ -223,7 +259,7 @@ class LastState:
return ClassgroupElement.get_default_element()
else:
return None
if self.subslot_end is not None:
elif self.state_type == StateType.END_OF_SUB_SLOT:
if chain == Chain.CHALLENGE_CHAIN or chain == Chain.REWARD_CHAIN:
return ClassgroupElement.get_default_element()
if chain == Chain.INFUSED_CHALLENGE_CHAIN:
@ -605,6 +641,10 @@ class Timelord:
self.unfinished_blocks.remove(block)
log.info(f"Generated infusion point for challenge: {challenge} iterations: {iteration}.")
if not self.last_state.can_infuse_sub_block():
log.warning("Too many sub-blocks, cannot infuse, discarding")
# Too many sub blocks
return
cc_info = dataclasses.replace(cc_info, number_of_iterations=ip_iters)
response = timelord_protocol.NewInfusionPointVDF(
challenge,
@ -623,7 +663,7 @@ class Timelord:
if (
self.last_state.get_last_block_total_iters() is None
and not self.last_state.first_sub_slot_no_peak
and not self.last_state.state_type == StateType.FIRST_SUB_SLOT
):
# We don't know when the last block was, so we can't make peaks
return
@ -637,7 +677,7 @@ class Timelord:
)
- (block.sub_slot_iters if overflow else 0)
)
if self.last_state.first_sub_slot_no_peak:
if self.last_state.state_type == StateType.FIRST_SUB_SLOT:
is_block = True
sub_block_height: uint32 = uint32(0)
else:
@ -660,7 +700,7 @@ class Timelord:
icc_info,
is_block,
)
if self.last_state.first_sub_slot_no_peak:
if self.last_state.state_type == StateType.FIRST_SUB_SLOT:
# Genesis
new_deficit = self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1
elif overflow and self.last_state.deficit == self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
@ -687,12 +727,17 @@ class Timelord:
else:
last_csb_or_eos = self.last_state.last_challenge_sb_or_eos_total_iters
if self.last_state.get_infused_sub_epoch_summary() is not None:
new_sub_epoch_summary = None
else:
new_sub_epoch_summary = block.sub_epoch_summary
self.new_peak = timelord_protocol.NewPeak(
new_reward_chain_sub_block,
block.difficulty,
new_deficit,
block.sub_slot_iters,
block.sub_epoch_summary,
new_sub_epoch_summary,
self.last_state.reward_challenge_cache,
last_csb_or_eos,
)
@ -752,10 +797,12 @@ class Timelord:
None if icc_ip_vdf is None else InfusedChallengeChainSubSlot(icc_ip_vdf)
)
icc_sub_slot_hash = icc_sub_slot.get_hash() if self.last_state.get_deficit() == 0 else None
if self.last_state.get_sub_epoch_summary() is not None:
ses_hash = self.last_state.get_sub_epoch_summary().get_hash()
new_sub_slot_iters = self.last_state.get_sub_epoch_summary().new_sub_slot_iters
new_difficulty = self.last_state.get_sub_epoch_summary().new_difficulty
next_ses: Optional[SubEpochSummary] = self.last_state.get_next_sub_epoch_summary()
if next_ses is not None:
log.error(f"INCLUDING SUB EPOCH SUMMARY {next_ses}")
ses_hash = next_ses.get_hash()
new_sub_slot_iters = next_ses.new_sub_slot_iters
new_difficulty = next_ses.new_difficulty
else:
ses_hash = None
new_sub_slot_iters = None
@ -782,11 +829,14 @@ class Timelord:
msg = Message("new_end_of_sub_slot_vdf", timelord_protocol.NewEndOfSubSlotVDF(eos_bundle))
await self.server.send_to_all([msg], NodeType.FULL_NODE)
log.info("Built end of subslot bundle.")
log.info(
f"Built end of subslot bundle. cc hash: {eos_bundle.challenge_chain.get_hash()}. New_difficulty: "
f"{eos_bundle.challenge_chain.new_difficulty} New ssi: {eos_bundle.challenge_chain.new_sub_slot_iters}"
)
if (
self.last_state.get_sub_epoch_summary() is None
or self.last_state.get_sub_epoch_summary().new_difficulty is None
self.last_state.get_infused_sub_epoch_summary() is None
or self.last_state.get_infused_sub_epoch_summary().new_difficulty is None
):
self.unfinished_blocks = self.overflow_blocks
else:

View File

@ -69,6 +69,7 @@ test_constants = DEFAULT_CONSTANTS.replace(
"DIFFICULTY_STARTING": 2 ** 9,
"DISCRIMINANT_SIZE_BITS": 16,
"SUB_EPOCH_SUB_BLOCKS": 140,
"MAX_SUB_SLOT_SUB_BLOCKS": 50,
"EPOCH_SUB_BLOCKS": 280,
"SUB_SLOT_TIME_TARGET": 600, # The target number of seconds per slot, mainnet 600
"SUB_SLOT_ITERS_STARTING": 2 ** 10, # Must be a multiple of 64
@ -441,11 +442,7 @@ class BlockTools:
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.required_iters,
block_list[-1],
constants, sub_blocks, height_to_hash, latest_sub_block.required_iters, block_list[-1], False
)
pending_ses = True

View File

@ -78,7 +78,7 @@ class TestGenesisBlock:
class TestBlockHeaderValidation:
@pytest.mark.asyncio
async def test_long_chain(self, empty_blockchain, default_1000_blocks):
blocks = bt.get_consecutive_blocks(380)
blocks = default_1000_blocks
for block in blocks:
if (
len(block.finished_sub_slots) > 0