Files
chia-blockchain/chia/consensus/make_sub_epoch_summary.py
T
Arvid NorbergandGitHub fe15e9ee6f [CHIA-4326] enable redundant expressions check in mypy (#21143)
* fix type annotation in configure.py and init.py

* enable mypy redundant-expr and remove redundant expressions
2026-07-29 09:37:24 -05:00

228 lines
8.6 KiB
Python

from __future__ import annotations
import logging
from chia_rs import BlockRecord, ConsensusConstants, FullBlock, SubEpochSummary, UnfinishedBlock
from chia_rs.sized_ints import uint8, uint32, uint64, uint128
from chia.consensus.blockchain_interface import BlockRecordsProtocol
from chia.consensus.challenge_tree import compute_challenge_merkle_root
from chia.consensus.deficit import calculate_deficit
from chia.consensus.difficulty_adjustment import (
_get_next_difficulty,
_get_next_sub_slot_iters,
can_finish_sub_and_full_epoch,
get_next_sub_slot_iters_and_difficulty,
height_can_be_first_in_epoch,
)
from chia.consensus.pot_iterations import calculate_ip_iters, calculate_sp_iters, is_overflow_block
log = logging.getLogger(__name__)
def make_sub_epoch_summary(
constants: ConsensusConstants,
blocks: BlockRecordsProtocol,
blocks_included_height: uint32,
prev_prev_block: BlockRecord,
new_difficulty: uint64 | None,
new_sub_slot_iters: uint64 | None,
*,
make_challenge_root: bool = False,
prev_ses_block: BlockRecord | None = None,
) -> SubEpochSummary:
"""
Creates a sub-epoch-summary object, assuming that the first block in the new sub-epoch is at height
"blocks_included_height". Prev_prev_b is the second to last block in the previous sub-epoch. On a new epoch,
new_difficulty and new_sub_slot_iters are also added.
Args:
constants: consensus constants being used for this chain
blocks: dictionary from header hash to SBR of all included SBR
blocks_included_height: block height in which the SES will be included
prev_prev_block: second to last block in epoch
new_difficulty: difficulty in new epoch
new_sub_slot_iters: sub slot iters in new epoch
"""
assert prev_prev_block.height == blocks_included_height - 2
# First sub_epoch
# This is not technically because more blocks can potentially be included than 2*MAX_SUB_SLOT_BLOCKS,
# But assuming less than 128 overflow blocks get infused in the first 2 slots, it's not an issue
if (blocks_included_height + constants.MAX_SUB_SLOT_BLOCKS) // constants.SUB_EPOCH_BLOCKS <= 1:
return SubEpochSummary(
constants.GENESIS_CHALLENGE,
constants.GENESIS_CHALLENGE,
uint8(0),
None,
None,
None, # No challenge root in first sub-epoch
)
if prev_ses_block is None:
curr: BlockRecord = prev_prev_block
while curr.sub_epoch_summary_included is None:
curr = blocks.block_record(curr.prev_hash)
prev_ses_block = curr
assert prev_ses_block is not None
assert prev_ses_block.sub_epoch_summary_included is not None
assert prev_ses_block.finished_reward_slot_hashes is not None
prev_ses = prev_ses_block.sub_epoch_summary_included.get_hash()
if make_challenge_root:
challenge_root = compute_challenge_merkle_root(blocks, blocks_included_height, prev_ses_block.height)
log.info(
f"make_sub_epoch_summary: height={blocks_included_height} >= fork_height={constants.HARD_FORK2_HEIGHT}, "
f"computed challenge_root={challenge_root.hex()}"
)
else:
challenge_root = None
log.info(
f"make_sub_epoch_summary: height={blocks_included_height} < fork_height={constants.HARD_FORK2_HEIGHT}, "
f"using None for challenge_root"
)
result = SubEpochSummary(
prev_ses,
prev_ses_block.finished_reward_slot_hashes[-1],
uint8(prev_ses_block.height % constants.SUB_EPOCH_BLOCKS),
new_difficulty,
new_sub_slot_iters,
challenge_root,
)
log.debug(f"make_sub_epoch_summary result hash: {result.get_hash().hex()}")
return result
def next_sub_epoch_summary(
constants: ConsensusConstants,
blocks: BlockRecordsProtocol,
required_iters: uint64,
block: UnfinishedBlock | FullBlock,
can_finish_soon: bool = False,
with_challenge_root: bool = False,
) -> SubEpochSummary | None:
"""
Returns the sub-epoch summary that can be included in the block after block. If it should include one. Block
must be eligible to be the last block in the epoch. If not, returns None. Assumes that there is a new slot
ending after block.
Args:
constants: consensus constants being used for this chain
blocks: interface to cached SBR
required_iters: required iters of the proof of space in block
block: the (potentially) last 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_BLOCKS)
Returns:
object: the new sub-epoch summary
"""
signage_point_index = block.reward_chain_block.signage_point_index
prev_b: BlockRecord | None = blocks.try_block_record(block.prev_header_hash)
if prev_b is None or prev_b.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_b is not None
# This is the ssi of the current block
sub_slot_iters = get_next_sub_slot_iters_and_difficulty(
constants, len(block.finished_sub_slots) > 0, prev_b, blocks
)[0]
overflow = is_overflow_block(constants, signage_point_index)
if (
len(block.finished_sub_slots) > 0
and block.finished_sub_slots[0].challenge_chain.subepoch_summary_hash is not None
):
return None
if can_finish_soon:
deficit: uint8 = uint8(0) # Assume that our deficit will go to zero soon
can_finish_se = True
if height_can_be_first_in_epoch(constants, uint32(prev_b.height + 2)):
can_finish_epoch = True
if (prev_b.height + 2) % constants.SUB_EPOCH_BLOCKS > 1:
curr: BlockRecord = prev_b
while curr.height % constants.SUB_EPOCH_BLOCKS > 0:
if (
curr.sub_epoch_summary_included is not None
and curr.sub_epoch_summary_included.new_difficulty is not None
):
can_finish_epoch = False
curr = blocks.block_record(curr.prev_hash)
if (
curr.sub_epoch_summary_included is not None
and curr.sub_epoch_summary_included.new_difficulty is not None
):
can_finish_epoch = False
elif height_can_be_first_in_epoch(constants, uint32(prev_b.height + constants.MAX_SUB_SLOT_BLOCKS + 2)):
can_finish_epoch = True
else:
can_finish_epoch = False
else:
deficit = calculate_deficit(
constants,
uint32(prev_b.height + 1),
prev_b,
overflow,
len(block.finished_sub_slots),
)
can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch(
constants,
blocks,
uint32(prev_b.height + 1),
prev_b.header_hash,
deficit,
False,
)
# can't finish se, no summary
if not can_finish_se:
return None
next_difficulty = None
next_sub_slot_iters = None
# if can finish epoch, new difficulty and ssi
if can_finish_epoch:
sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index)
ip_iters = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters)
next_difficulty = _get_next_difficulty(
constants,
blocks,
block.prev_header_hash,
uint32(prev_b.height + 1),
uint64(prev_b.weight - blocks.block_record(prev_b.prev_hash).weight),
deficit,
False, # Already checked above
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,
blocks,
block.prev_header_hash,
uint32(prev_b.height + 1),
sub_slot_iters,
deficit,
False, # Already checked above
True,
uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)),
True,
)
return make_sub_epoch_summary(
constants,
blocks,
uint32(prev_b.height + 2),
prev_b,
next_difficulty,
next_sub_slot_iters,
make_challenge_root=with_challenge_root,
)