Many timelord and full node fixes to difficulty and sub epochs
This commit is contained in:
parent
2c4eb44cea
commit
075498a044
|
@ -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} "
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue