Switch to signage point based iters

This commit is contained in:
Mariano Sorgente 2020-11-20 15:16:32 +09:00 committed by Yostra
parent 06109829a8
commit a55649847f
26 changed files with 894 additions and 810 deletions

View File

@ -9,7 +9,7 @@ class ConsensusConstants:
SLOT_SUB_BLOCKS_TARGET: uint32
MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: uint32
MAX_SLOT_SUB_BLOCKS: uint32
NUM_CHECKPOINTS_PER_SLOT: uint32
NUM_SPS_SUB_SLOT: uint32
IPS_STARTING: uint64
DIFFICULTY_STARTING: uint64
@ -22,7 +22,7 @@ class ConsensusConstants:
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
EXTRA_ITERS_TIME_TARGET: float
NUM_SP_INTERVALS_EXTRA: int
MAX_FUTURE_TIME: int # The next block can have a timestamp of at most these many seconds more
NUMBER_OF_TIMESTAMPS: int # Than the average of the last NUMBER_OF_TIMESTAMPS blocks
FIRST_CC_CHALLENGE: bytes

View File

@ -2,7 +2,12 @@ from .constants import ConsensusConstants
testnet_kwargs = {
"NUMBER_OF_HEADS": 3, # The number of tips each full node keeps track of and propagates
# TODO(mariano): write comments here
"SLOT_SUB_BLOCKS_TARGET": 16,
"MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK": 5,
"MAX_SLOT_SUB_BLOCKS": 64,
"NUM_SPS_SUB_SLOT": 32,
"IPS_STARTING": 2 ** 18,
# DIFFICULTY_STARTING is the starting difficulty for the first epoch, which is then further
# multiplied by another factor of 2^32, to be used in the VDF iter calculation formula.
"DIFFICULTY_STARTING": 2 ** 20,
@ -16,7 +21,7 @@ testnet_kwargs = {
"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
"EXTRA_ITERS_TIME_TARGET": 37.5,
"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
"FIRST_CC_CHALLENGE": bytes([0x00] * 32),

View File

@ -1,77 +1,60 @@
from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32
from src.util.hash import std_hash
from src.util.ints import uint64, uint128, uint8
from src.consensus.pos_quality import quality_str_to_quality
from src.consensus.constants import ConsensusConstants
def is_overflow_sub_block(constants: ConsensusConstants, ips: uint64, required_iters: uint64) -> bool:
def is_overflow_sub_block(constants: ConsensusConstants, signage_point_index: uint8) -> bool:
if signage_point_index >= constants.NUM_SPS_SUB_SLOT:
raise ValueError("SP index too high")
return signage_point_index >= constants.NUM_SPS_SUB_SLOT - constants.NUM_SP_INTERVALS_EXTRA
def calculate_sp_interval_iters(constants: ConsensusConstants, ips: uint64) -> uint64:
sub_slot_iters: uint64 = calculate_sub_slot_iters(constants, ips)
if required_iters >= sub_slot_iters:
raise ValueError(f"Required iters {required_iters} is not below the slot iterations")
extra_iters: uint64 = uint64(int(float(ips) * constants.EXTRA_ITERS_TIME_TARGET))
return required_iters + extra_iters >= sub_slot_iters
return uint64(sub_slot_iters // constants.NUM_SPS_SUB_SLOT)
def calculate_sub_slot_iters(constants: ConsensusConstants, ips: uint64) -> uint64:
return ips * constants.SLOT_TIME_TARGET
def calculate_sp_iters(constants: ConsensusConstants, ips: uint64, required_iters: uint64) -> uint64:
sub_slot_iters: uint64 = calculate_sub_slot_iters(constants, ips)
if required_iters >= sub_slot_iters:
raise ValueError(f"Required iters {required_iters} is not below the slot iterations")
checkpoint_size: uint64 = uint64(sub_slot_iters // constants.NUM_CHECKPOINTS_PER_SLOT)
checkpoint_index: int = required_iters // checkpoint_size
if checkpoint_index >= constants.NUM_CHECKPOINTS_PER_SLOT:
# Checkpoints don't divide sub_slot_iters cleanly, so we return the last checkpoint
return required_iters - required_iters % checkpoint_size - checkpoint_size
else:
return required_iters - required_iters % checkpoint_size
def calculate_sp_iters(constants: ConsensusConstants, ips: uint64, signage_point_index: uint8) -> uint64:
if signage_point_index >= constants.NUM_SPS_SUB_SLOT:
raise ValueError("SP index too high")
return uint64(calculate_sp_interval_iters(constants, ips) * signage_point_index)
def calculate_sp_index(constants: ConsensusConstants, ips: uint64, required_iters: uint64) -> uint8:
sp_iters = calculate_sp_iters(constants, ips, required_iters)
sub_slot_iters: uint64 = calculate_sub_slot_iters(constants, ips)
checkpoint_size: uint64 = uint64(sub_slot_iters // constants.NUM_CHECKPOINTS_PER_SLOT)
assert sp_iters % checkpoint_size == 0
target_index = sp_iters // checkpoint_size
return uint8(target_index)
def calculate_ip_iters(constants: ConsensusConstants, ips: uint64, required_iters: uint64) -> uint64:
def calculate_ip_iters(
constants: ConsensusConstants, ips: uint64, signage_point_index: uint8, required_iters: uint64
) -> uint64:
# Note that the IPS is for the block passed in, which might be in the previous epoch
sub_slot_iters: uint64 = calculate_sub_slot_iters(constants, ips)
if required_iters >= sub_slot_iters:
raise ValueError(f"Required iters {required_iters} is not below the slot iterations")
extra_iters: uint64 = uint64(int(float(ips) * constants.EXTRA_ITERS_TIME_TARGET))
return (required_iters + extra_iters) % sub_slot_iters
sp_iters = calculate_sp_iters(constants, ips, signage_point_index)
sp_interval_iters: uint64 = calculate_sp_interval_iters(constants, ips)
if sp_iters % sp_interval_iters != 0 or sp_iters >= sub_slot_iters:
raise ValueError(f"Invalid sp iters {sp_iters} for this ips {ips}")
if required_iters >= sp_interval_iters or required_iters == 0:
raise ValueError(
f"Required iters {required_iters} is not below the sp interval iters {sp_interval_iters} {ips} or not >0."
)
return (sp_iters + constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters) % sub_slot_iters
def calculate_iterations_quality(
quality: bytes32,
quality_string: bytes32,
size: int,
difficulty: int,
cc_sp_output_hash: bytes32,
) -> uint64:
"""
Calculates the number of iterations from the quality. The quality is converted to a number
between 0 and 1, then divided by expected plot size, and finally multiplied by the
difficulty.
"""
iters = uint64(uint128(int(difficulty) << 32) // quality_str_to_quality(quality, size))
sp_quality_string: bytes32 = std_hash(quality_string + cc_sp_output_hash)
iters = uint64(uint128(int(difficulty) << 32) // quality_str_to_quality(sp_quality_string, size))
return max(iters, uint64(1))
def calculate_iterations(
constants: ConsensusConstants,
proof_of_space: ProofOfSpace,
difficulty: int,
) -> uint64:
"""
Convenience function to calculate the number of iterations using the proof instead
of the quality. The quality must be retrieved from the proof.
"""
quality: bytes32 = proof_of_space.verify_and_get_quality_string(constants, None, None)
assert quality is not None
return calculate_iterations_quality(quality, proof_of_space.size, difficulty)

View File

@ -8,7 +8,7 @@ from src.util.keychain import Keychain
from src.consensus.constants import ConsensusConstants
from src.consensus.pot_iterations import (
calculate_iterations_quality,
calculate_sp_index,
calculate_sp_interval_iters,
)
from src.protocols import farmer_protocol, harvester_protocol
from src.server.connection import PeerConnections
@ -17,7 +17,7 @@ from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32
from src.types.pool_target import PoolTarget
from src.util.api_decorators import api_request
from src.util.ints import uint32, uint64, uint8
from src.util.ints import uint32, uint64
from src.wallet.derive_keys import master_sk_to_farmer_sk, master_sk_to_pool_sk
from src.util.chech32 import decode_puzzle_hash
@ -38,14 +38,11 @@ class Farmer:
consensus_constants: ConsensusConstants,
):
self.config = farmer_config
# To send to harvester on connect
self.latest_challenge: bytes32 = None
# Keep track of all sps for each challenge
self.sps: Dict[bytes32, List[farmer_protocol.NewSignagePoint]] = {}
# Keep track of harvester plot identifier (str), target sp index, and PoSpace for each challenge
self.proofs_of_space: Dict[bytes32, List[Tuple[str, uint8, ProofOfSpace]]] = {}
self.proofs_of_space: Dict[bytes32, List[Tuple[str, ProofOfSpace]]] = {}
# Quality string to plot identifier and challenge_hash, for use with harvester.RequestSignatures
self.quality_str_to_identifiers: Dict[bytes32, Tuple[str, bytes32]] = {}
@ -94,17 +91,8 @@ class Farmer:
msg = harvester_protocol.HarvesterHandshake(
self._get_public_keys(),
self.pool_public_keys,
self.config["pool_share_threshold"],
)
yield OutboundMessage(NodeType.HARVESTER, Message("harvester_handshake", msg), Delivery.RESPOND)
if self.latest_challenge is not None and len(self.sps[self.latest_challenge]) > 0:
sp = self.sps[self.latest_challenge][0]
message = harvester_protocol.NewChallenge(sp.challenge_hash, sp.difficulty, sp.slot_iterations)
yield OutboundMessage(
NodeType.HARVESTER,
Message("new_challenge", message),
Delivery.BROADCAST,
)
def _set_global_connections(self, global_connections: PeerConnections):
self.global_connections: PeerConnections = global_connections
@ -129,93 +117,81 @@ class Farmer:
return [master_sk_to_farmer_sk(sk) for sk, _ in all_sks] + [master_sk_to_pool_sk(sk) for sk, _ in all_sks]
@api_request
async def challenge_response(self, challenge_response: harvester_protocol.ChallengeResponse):
async def new_proof_of_space(self, new_proof_of_space: harvester_protocol.NewProofOfSpace):
"""
This is a response from the harvester, for a NewChallenge. Here we check if the proof
of space is sufficiently good, and if so, we ask for the whole proof.
"""
if challenge_response.proof.challenge_hash not in self.number_of_responses:
self.number_of_responses[challenge_response.proof.challenge_hash] = 0
if new_proof_of_space.proof.challenge_hash not in self.number_of_responses:
self.number_of_responses[new_proof_of_space.proof.challenge_hash] = 0
if self.number_of_responses[challenge_response.proof.challenge_hash] >= 32:
if self.number_of_responses[new_proof_of_space.proof.challenge_hash] >= 32:
log.warning(
f"Surpassed 32 PoSpace for one challenge, no longer submitting PoSpace for challenge "
f"{challenge_response.proof.challenge_hash}"
f"{new_proof_of_space.proof.challenge_hash}"
)
return
difficulty: uint64 = uint64(0)
for sp in self.sps[challenge_response.proof.challenge_hash]:
if sp.challenge_hash == challenge_response.proof.challenge_hash:
for sp in self.sps[new_proof_of_space.proof.challenge_hash]:
if sp.challenge_hash == new_proof_of_space.proof.challenge_hash:
difficulty = sp.difficulty
if difficulty == 0:
log.error(f"Did not find challenge {challenge_response.proof.challenge_hash}")
log.error(f"Did not find challenge {new_proof_of_space.proof.challenge_hash}")
return
computed_quality_string = challenge_response.proof.verify_and_get_quality_string(self.constants, None, None)
computed_quality_string = new_proof_of_space.proof.verify_and_get_quality_string(self.constants)
if computed_quality_string is None:
log.error(f"Invalid proof of space {challenge_response.proof}")
log.error(f"Invalid proof of space {new_proof_of_space.proof}")
return
required_iters: uint64 = calculate_iterations_quality(
computed_quality_string,
challenge_response.proof.size,
difficulty,
)
if challenge_response.proof.challenge_hash not in self.sps:
log.warning(f"Received response for challenge that we do not have {challenge_response.challenge_hash}")
if new_proof_of_space.proof.challenge_hash not in self.sps:
log.warning(f"Received response for challenge that we do not have {new_proof_of_space.challenge_hash}")
return
elif len(self.sps[challenge_response.proof.challenge_hash]) == 0:
log.warning(f"Received response for challenge {challenge_response.challenge_hash} with no sp data")
elif len(self.sps[new_proof_of_space.proof.challenge_hash]) == 0:
log.warning(f"Received response for challenge {new_proof_of_space.challenge_hash} with no sp data")
return
sub_slot_iters = self.sps[challenge_response.proof.challenge_hash][0].slot_iterations
# Double check that the iters are good
if required_iters < sub_slot_iters or required_iters < self.config["pool_share_threshold"]:
self.number_of_responses[challenge_response.proof.challenge_hash] += 1
self._state_changed("challenge")
ips = sub_slot_iters // self.constants.SLOT_TIME_TARGET
# This is the sp which this proof of space is assigned to
target_sp_index: uint8 = calculate_sp_index(self.constants, ips, required_iters)
self.number_of_responses[new_proof_of_space.proof.challenge_hash] += 1
self._state_changed("challenge")
# This is the sp which this proof of space is assigned to
# Requests signatures for the first sp (maybe the second if we were really slow at getting proofs)
for sp in self.sps[challenge_response.proof.challenge_hash]:
# If we already have the target sp, proceed at getting the signatures for this PoSpace
if sp.signage_point_index == target_sp_index:
request = harvester_protocol.RequestSignatures(
challenge_response.plot_identifier,
challenge_response.proof.challenge_hash,
[sp.challenge_chain_sp, sp.reward_chain_sp],
)
yield OutboundMessage(
NodeType.HARVESTER,
Message("request_signatures", request),
Delivery.RESPOND,
)
if challenge_response.proof.challenge_hash not in self.proofs_of_space:
self.proofs_of_space[challenge_response.proof.challenge_hash] = [
(
challenge_response.plot_identifier,
target_sp_index,
challenge_response.proof,
)
]
else:
self.proofs_of_space[challenge_response.proof.challenge_hash].append(
(
challenge_response.plot_identifier,
target_sp_index,
challenge_response.proof,
)
)
self.quality_str_to_identifiers[computed_quality_string] = (
challenge_response.plot_identifier,
challenge_response.proof.challenge_hash,
# Requests signatures for the first sp (maybe the second if we were really slow at getting proofs)
for sp in self.sps[new_proof_of_space.proof.challenge_hash]:
required_iters: uint64 = calculate_iterations_quality(
computed_quality_string, new_proof_of_space.proof.size, difficulty, sp.challenge_hash
)
assert required_iters < calculate_sp_interval_iters(sp.slot_iterations, sp.ips)
# If we already have the target sp, proceed at getting the signatures for this PoSpace
request = harvester_protocol.RequestSignatures(
new_proof_of_space.plot_identifier,
new_proof_of_space.proof.challenge_hash,
[sp.challenge_chain_sp, sp.reward_chain_sp],
)
yield OutboundMessage(
NodeType.HARVESTER,
Message("request_signatures", request),
Delivery.RESPOND,
)
if new_proof_of_space.proof.challenge_hash not in self.proofs_of_space:
self.proofs_of_space[new_proof_of_space.proof.challenge_hash] = [
(
new_proof_of_space.plot_identifier,
new_proof_of_space.proof,
)
]
else:
log.warning(f"Required_iters: {required_iters}, too high. Must be < sub_slot_iters={sub_slot_iters}")
self.proofs_of_space[new_proof_of_space.proof.challenge_hash].append(
(
new_proof_of_space.plot_identifier,
new_proof_of_space.proof,
)
)
self.quality_str_to_identifiers[computed_quality_string] = (
new_proof_of_space.plot_identifier,
new_proof_of_space.proof.challenge_hash,
)
@api_request
async def respond_signatures(self, response: harvester_protocol.RespondSignatures):
@ -335,17 +311,19 @@ class Farmer:
@api_request
async def signage_point(self, signage_point: farmer_protocol.NewSignagePoint):
message = harvester_protocol.NewSignagePoint(
signage_point.challenge_hash,
signage_point.difficulty,
signage_point.ips,
signage_point.signage_point_index,
signage_point.challenge_chain_sp,
)
yield OutboundMessage(
NodeType.HARVESTER,
Message("new_challenge", message),
Delivery.BROADCAST,
)
if signage_point.challenge_hash not in self.seen_challenges:
message = harvester_protocol.NewChallenge(
signage_point.challenge_hash,
signage_point.difficulty,
signage_point.slot_iterations,
)
yield OutboundMessage(
NodeType.HARVESTER,
Message("new_challenge", message),
Delivery.BROADCAST,
)
self.seen_challenges.add(signage_point.challenge_hash)
# This allows time for the collection of proofs from the harvester
self._state_changed("challenge")
@ -355,33 +333,10 @@ class Farmer:
await asyncio.sleep(1)
self._state_changed("challenge")
if self.latest_challenge != signage_point.challenge_hash:
self.latest_challenge = signage_point.challenge_hash
if self.latest_challenge not in self.sps:
self.sps[self.latest_challenge] = [signage_point]
if signage_point.challenge_hash not in self.sps:
self.sps[signage_point.challenge_hash] = [signage_point]
else:
self.sps[self.latest_challenge].append(signage_point)
# We already have fetched proofs for this challenge
if signage_point.challenge_hash in self.proofs_of_space:
for plot_identifier, target_sp_index, pospace in self.proofs_of_space[signage_point.challenge_hash]:
if target_sp_index == signage_point.signage_point_index:
# Only proceeds with proofs of space that can be infused at this infusion point
request = harvester_protocol.RequestSignatures(
plot_identifier,
signage_point.challenge_hash,
[
signage_point.challenge_chain_sp,
signage_point.reward_chain_sp,
],
)
yield OutboundMessage(
NodeType.HARVESTER,
Message("request_signatures", request),
Delivery.BROADCAST,
)
self.sps[signage_point.challenge_hash].append(signage_point)
@api_request
async def request_signed_values(self, full_node_request: farmer_protocol.RequestSignedValues):

View File

@ -15,7 +15,6 @@ from src.full_node.cost_calculator import calculate_cost_of_program
from src.full_node.mempool_check_conditions import get_name_puzzle_conditions
from src.full_node.signage_point import SignagePoint
from src.full_node.sub_block_record import SubBlockRecord
from src.types.classgroup import ClassgroupElement
from src.types.coin import Coin, hash_coin_list
from src.types.end_of_slot_bundle import EndOfSubSlotBundle
from src.types.foliage import FoliageSubBlock, FoliageBlock, TransactionsInfo, FoliageSubBlockData
@ -29,10 +28,9 @@ from src.types.spend_bundle import SpendBundle
from src.types.unfinished_block import UnfinishedBlock
from src.types.vdf import VDFInfo, VDFProof
from src.util.hash import std_hash
from src.util.ints import uint128, uint64, uint32
from src.util.ints import uint128, uint64, uint32, uint8
from src.util.merkle_set import MerkleSet
from src.util.prev_block import get_prev_block
from src.util.vdf_prover import get_vdf_info_and_proof
from tests.recursive_replace import recursive_replace
@ -200,6 +198,7 @@ def create_foliage(
def create_unfinished_block(
constants: ConsensusConstants,
sub_slot_start_total_iters: uint128,
signage_point_index: uint8,
sp_iters: uint64,
ip_iters: uint64,
proof_of_space: ProofOfSpace,
@ -208,14 +207,14 @@ def create_unfinished_block(
pool_target: PoolTarget,
get_plot_signature: Callable[[bytes32, G1Element], G2Element],
get_pool_signature: Callable[[PoolTarget, G1Element], G2Element],
signage_point: SignagePoint,
timestamp: Optional[uint64] = None,
seed: bytes32 = b"",
spend_bundle: Optional[SpendBundle] = None,
prev_sub_block: Optional[SubBlockRecord] = None,
sub_blocks=None,
finished_sub_slots=None,
signage_point: Optional[SignagePoint] = None,
) -> Optional[UnfinishedBlock]:
) -> UnfinishedBlock:
overflow = sp_iters > ip_iters
total_iters_sp = sub_slot_start_total_iters + sp_iters
prev_block: Optional[SubBlockRecord] = None # Set this if we are a block
@ -235,83 +234,9 @@ def create_unfinished_block(
cc_sp_hash: Optional[bytes32] = slot_cc_challenge
# Only enters this if statement if we are in testing mode (making VDF proofs here)
if sp_iters != 0 and signage_point is None:
if is_genesis:
cc_vdf_input = ClassgroupElement.get_default_element()
if len(finished_sub_slots) == 0:
cc_vdf_challenge = constants.FIRST_CC_CHALLENGE
rc_vdf_challenge = constants.FIRST_RC_CHALLENGE
else:
cc_vdf_challenge = finished_sub_slots[-1].challenge_chain.get_hash()
rc_vdf_challenge = finished_sub_slots[-1].reward_chain.get_hash()
sp_vdf_iters = sp_iters
elif new_sub_slot and not overflow:
# Start from start of this slot. Case of no overflow slots. Also includes genesis block after
# empty slot (but not overflowing)
rc_vdf_challenge: bytes32 = finished_sub_slots[-1].reward_chain.get_hash()
cc_vdf_challenge = finished_sub_slots[-1].challenge_chain.get_hash()
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
elif new_sub_slot and overflow and len(finished_sub_slots) > 1:
# Start from start of prev slot. Rare case of empty prev slot.
# Includes genesis block after 2 empty slots
rc_vdf_challenge = finished_sub_slots[-2].reward_chain.get_hash()
cc_vdf_challenge = finished_sub_slots[-2].challenge_chain.get_hash()
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
else:
if new_sub_slot and overflow:
num_sub_slots_to_look_for = 1 # Starting at prev will skip 1 sub-slot
elif not new_sub_slot and overflow:
num_sub_slots_to_look_for = 2 # Starting at prev does not skip any sub slots
elif not new_sub_slot and not overflow:
num_sub_slots_to_look_for = 1 # Starting at prev does not skip any sub slots, but we should not go back
else:
assert False
next_sb: SubBlockRecord = prev_sub_block
curr: SubBlockRecord = next_sb
# Finds a sub-block which is BEFORE our signage point, otherwise goes back to the end of sub-slot
# Note that for overflow sub-blocks, we are looking at the end of the previous sub-slot
while num_sub_slots_to_look_for > 0:
next_sb = curr
if curr.first_in_sub_slot:
num_sub_slots_to_look_for -= 1
if curr.total_iters < total_iters_sp:
break
if curr.height == 0:
break
curr = sub_blocks[curr.prev_hash]
if curr.total_iters < total_iters_sp:
sp_vdf_iters = total_iters_sp - curr.total_iters
cc_vdf_input = curr.challenge_vdf_output
rc_vdf_challenge = curr.reward_infusion_new_challenge
else:
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
rc_vdf_challenge = next_sb.finished_reward_slot_hashes[-1]
while not curr.first_in_sub_slot:
curr = sub_blocks[curr.prev_hash]
cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1]
cc_sp_vdf, cc_sp_proof = get_vdf_info_and_proof(
constants,
cc_vdf_input,
cc_vdf_challenge,
sp_vdf_iters,
)
rc_sp_vdf, rc_sp_proof = get_vdf_info_and_proof(
constants,
ClassgroupElement.get_default_element(),
rc_vdf_challenge,
sp_vdf_iters,
)
cc_sp_hash = cc_sp_vdf.output.get_hash()
rc_sp_hash = rc_sp_vdf.output.get_hash()
signage_point = SignagePoint(cc_sp_vdf, cc_sp_proof, rc_sp_vdf, rc_sp_proof)
if signage_point.cc_vdf is not None:
cc_sp_hash = signage_point.cc_vdf.output.get_hash()
rc_sp_hash = signage_point.rc_vdf.output.get_hash()
else:
if new_sub_slot:
rc_sp_hash = finished_sub_slots[-1].reward_chain.get_hash()
@ -331,16 +256,14 @@ def create_unfinished_block(
assert rc_sp_signature is not None
assert blspy.AugSchemeMPL.verify(proof_of_space.plot_public_key, cc_sp_hash, cc_sp_signature)
# Checks sp filter
plot_id = proof_of_space.get_plot_id()
if not ProofOfSpace.can_create_proof(
constants, plot_id, proof_of_space.challenge_hash, cc_sp_hash, cc_sp_signature
):
return None
# print(
# f"Calculating iters.. sssti {sub_slot_start_total_iters} ip iters {ip_iters} overflow {overflow} {prev_sub_slot_iters}"
# )
total_iters = uint128(sub_slot_start_total_iters + ip_iters + (prev_sub_slot_iters if overflow else 0))
rc_sub_block = RewardChainSubBlockUnfinished(
total_iters,
signage_point_index,
proof_of_space,
signage_point.cc_vdf,
cc_sp_signature,
@ -405,6 +328,7 @@ def unfinished_block_to_full_block(
new_weight,
new_height,
unfinished_block.reward_chain_sub_block.total_iters,
unfinished_block.reward_chain_sub_block.signage_point_index,
unfinished_block.reward_chain_sub_block.proof_of_space,
unfinished_block.reward_chain_sub_block.challenge_chain_sp_vdf,
unfinished_block.reward_chain_sub_block.challenge_chain_sp_signature,

View File

@ -78,8 +78,6 @@ async def validate_unfinished_header_block(
q_str: Optional[bytes32] = header_block.reward_chain_sub_block.proof_of_space.verify_and_get_quality_string(
constants,
cc_sp_hash,
header_block.reward_chain_sub_block.challenge_chain_sp_signature,
)
if q_str is None:
return None, ValidationError(Err.INVALID_POSPACE)
@ -89,14 +87,23 @@ async def validate_unfinished_header_block(
q_str,
header_block.reward_chain_sub_block.proof_of_space.size,
difficulty,
cc_sp_hash,
)
# 3. check signage point index
if (
header_block.reward_chain_sub_block.signage_point_index < 0
or header_block.reward_chain_sub_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT
):
return None, ValidationError(Err.INVALID_SP_INDEX)
sp_iters: uint64 = calculate_sp_iters(constants, ips, required_iters)
ip_iters: uint64 = calculate_ip_iters(constants, ips, required_iters)
sp_iters: uint64 = calculate_sp_iters(constants, ips, header_block.reward_chain_sub_block.signage_point_index)
ip_iters: uint64 = calculate_ip_iters(
constants, ips, header_block.reward_chain_sub_block.signage_point_index, required_iters
)
sub_slot_iters: uint64 = calculate_sub_slot_iters(constants, ips)
overflow = is_overflow_sub_block(constants, ips, required_iters)
overflow = is_overflow_sub_block(constants, header_block.reward_chain_sub_block.signage_point_index)
# 3. Check finished slots that have been crossed since prev_sb
# 4. Check finished slots that have been crossed since prev_sb
ses_hash: Optional[bytes32] = None
if new_sub_slot:
# Finished a slot(s) since previous block. The first sub-slot must have at least one sub-block, and all
@ -107,7 +114,7 @@ async def validate_unfinished_header_block(
if finished_sub_slot_n == 0:
if genesis_block:
# 3a. check sub-slot challenge hash for genesis block
# 4a. check sub-slot challenge hash for genesis block
if challenge_hash != constants.FIRST_CC_CHALLENGE:
return None, ValidationError(Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
else:
@ -115,11 +122,11 @@ async def validate_unfinished_header_block(
while not curr.first_in_sub_slot:
curr = sub_blocks[curr.prev_hash]
# 3b. check sub-slot challenge hash for non-genesis block
# 4b. check sub-slot challenge hash for non-genesis block
if not curr.finished_challenge_slot_hashes[-1] == challenge_hash:
return None, ValidationError(Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
else:
# 3c. check sub-slot challenge hash for empty slot
# 4c. check sub-slot challenge hash for empty slot
if (
not header_block.finished_sub_slots[finished_sub_slot_n - 1].challenge_chain.get_hash()
== challenge_hash
@ -127,7 +134,7 @@ async def validate_unfinished_header_block(
return None, ValidationError(Err.INVALID_PREV_CHALLENGE_SLOT_HASH)
if genesis_block:
# 3d. Validate that genesis block has no ICC
# 4d. Validate that genesis block has no ICC
if sub_slot.infused_challenge_chain is not None:
return None, ValidationError(Err.SHOULD_NOT_HAVE_ICC)
else:
@ -145,15 +152,15 @@ async def validate_unfinished_header_block(
curr = sub_blocks[curr.prev_hash]
if curr.is_challenge_sub_block(constants):
icc_challenge_hash = curr.challenge_block_info_hash
ip_iters_challenge_block = calculate_ip_iters(constants, curr.ips, curr.required_iters)
icc_iters_committed: uint64 = (
calculate_sub_slot_iters(constants, prev_sb.ips) - ip_iters_challenge_block
)
icc_iters_committed: uint64 = calculate_sub_slot_iters(
constants, prev_sb.ips
) - curr.ip_iters(constants)
else:
icc_challenge_hash = curr.finished_infused_challenge_slot_hashes[-1]
icc_iters_committed = calculate_sub_slot_iters(constants, prev_sb.ips)
ip_iters_prev = calculate_ip_iters(constants, prev_sb.ips, prev_sb.required_iters)
icc_iters_proof: uint64 = calculate_sub_slot_iters(constants, prev_sb.ips) - ip_iters_prev
icc_iters_proof: uint64 = calculate_sub_slot_iters(constants, prev_sb.ips) - prev_sb.ip_iters(
constants
)
if prev_sb.is_challenge_sub_block(constants):
icc_vdf_input = ClassgroupElement.get_default_element()
else:
@ -172,11 +179,11 @@ async def validate_unfinished_header_block(
icc_iters_proof = icc_iters_committed
icc_vdf_input = ClassgroupElement.get_default_element()
# 3e. Validate that there is not icc iff icc_challenge hash is None
# 4e. Validate that there is not icc iff icc_challenge hash is None
assert (sub_slot.infused_challenge_chain is None) == (icc_challenge_hash is None)
if sub_slot.infused_challenge_chain is not None:
assert icc_vdf_input is not None
# 2c. Check infused challenge chain sub-slot VDF
# 4f. Check infused challenge chain sub-slot VDF
# Only validate from prev_sb to optimize
target_vdf_info = VDFInfo(
icc_challenge_hash,
@ -195,7 +202,7 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_ICC_EOS_VDF)
# 2d. Check infused challenge sub-slot hash in challenge sub-slot
# 4g. Check infused challenge sub-slot hash in challenge sub-slot
if sub_slot.reward_chain.deficit == constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
if (
sub_slot.infused_challenge_chain.get_hash()
@ -206,7 +213,7 @@ async def validate_unfinished_header_block(
if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None:
return None, ValidationError(Err.INVALID_ICC_HASH_CC)
# 2e. Check infused challenge sub-slot hash in reward sub-slot
# 4h. 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
@ -223,12 +230,12 @@ 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
# 2f. check sub-epoch summary hash is None for empty slots
# 4i. 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)
# 2g. Check new difficulty
# 4j. Check new difficulty
if finishes_epoch:
if sub_slot.challenge_chain.new_ips != ips:
return None, ValidationError(Err.INVALID_NEW_IPS)
@ -240,14 +247,14 @@ async def validate_unfinished_header_block(
if sub_slot.challenge_chain.new_difficulty is not None:
return None, ValidationError(Err.INVALID_NEW_DIFFICULTY)
# 2h. Check challenge sub-slot hash in reward sub-slot
# 4k. 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"
)
# 2i. Check challenge chain sub-slot VDF
# 2j. Check end of reward slot VDF
# 4l. Check challenge chain sub-slot VDF
# 4m. 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()
@ -268,7 +275,7 @@ async def validate_unfinished_header_block(
# the same IPS as the previous block, since it's the same slot
rc_eos_vdf_challenge: bytes32 = prev_sb.reward_infusion_new_challenge
sub_slot_iters = calculate_sub_slot_iters(constants, prev_sb.ips)
eos_vdf_iters = sub_slot_iters - calculate_ip_iters(constants, prev_sb.ips, prev_sb.required_iters)
eos_vdf_iters = sub_slot_iters - prev_sb.ip_iters(constants)
cc_start_element: ClassgroupElement = prev_sb.challenge_vdf_output
else:
# At least one empty slot, so use previous slot hash. IPS might change because it's a new slot
@ -304,7 +311,7 @@ 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)
# 2k. Check deficit (5 deficit edge case for genesis block)
# 4n. Check deficit (5 deficit edge case for genesis block)
if genesis_block:
if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK:
return None, ValidationError(
@ -326,25 +333,25 @@ async def validate_unfinished_header_block(
if sub_slot.reward_chain.deficit != prev_sb.deficit:
return None, ValidationError(Err.INVALID_DEFICIT, "deficit is wrong at slot end")
# 3. Check sub-epoch summary
# 5. Check sub-epoch summary
# Note that the subepoch summary is the summary of the previous subepoch (not the one that just finished)
if ses_hash is not None:
# 3a. Check that genesis block does not have sub-epoch summary
# 5a. 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")
# 3b. Check that we finished a slot and we finished a sub-epoch
# 5b. Check that we finished a slot and we finished a sub-epoch
if not new_sub_slot or not finishes_se:
return None, ValidationError(
Err.INVALID_SUB_EPOCH_SUMMARY, f"new sub-slot: {new_sub_slot} finishes sub-epoch {finishes_se}"
)
# 3c. Check the actual sub-epoch is correct
# 5c. Check the actual sub-epoch is correct
expected_sub_epoch_summary = make_sub_epoch_summary(
constants,
sub_blocks,
uint32(prev_sb.height + 1),
prev_sb,
sub_blocks[prev_sb.prev_hash],
difficulty if finishes_epoch else None,
ips if finishes_epoch else None,
)
@ -354,7 +361,7 @@ async def validate_unfinished_header_block(
Err.INVALID_SUB_EPOCH_SUMMARY, f"expected ses hash: {expected_hash} got {ses_hash} "
)
elif new_sub_slot and not genesis_block:
# 3d. Check that we don't have to include a sub-epoch summary
# 5d. Check that we don't have to include a sub-epoch summary
if finishes_sub_epoch(constants, prev_sb.height, prev_sb.deficit, False, sub_blocks, prev_sb.prev_hash):
return None, ValidationError(
Err.INVALID_SUB_EPOCH_SUMMARY, "block finishes sub-epoch but ses-hash is None"
@ -364,7 +371,7 @@ async def validate_unfinished_header_block(
# Blocks with very low required iters are not overflow blocks
assert not overflow
# 5. Check no overflows in the first sub-slot of a new epoch (although they are OK in the second sub-slot)
# 6. Check no overflows in the first sub-slot of a new epoch (although they are OK in the second sub-slot)
if overflow and finishes_epoch and len(header_block.finished_sub_slots) < 2:
return None, ValidationError(Err.NO_OVERFLOWS_IN_FIRST_SUB_SLOT_NEW_EPOCH)
@ -399,58 +406,62 @@ async def validate_unfinished_header_block(
challenge = reversed_challenge_hashes[challenges_to_look_for - 1]
assert challenge is not None
# 6. Check challenge in proof of space is valid
# 7. Check challenge in proof of space is valid
if challenge != header_block.reward_chain_sub_block.proof_of_space.challenge_hash:
return None, ValidationError(Err.INVALID_POSPACE_CHALLENGE)
# 7. Check total iters
# 8. Check total iters
if genesis_block:
total_iters: uint128 = uint128(
constants.IPS_STARTING * constants.SLOT_TIME_TARGET * len(header_block.finished_sub_slots)
)
else:
prev_sb_iters = calculate_ip_iters(constants, prev_sb.ips, prev_sb.required_iters)
if new_sub_slot:
total_iters: uint128 = prev_sb.total_iters
prev_sb_slot_iters = calculate_sub_slot_iters(constants, prev_sb.ips)
# Add the rest of the slot of prev_sb
total_iters += prev_sb_slot_iters - prev_sb_iters
total_iters += prev_sb_slot_iters - prev_sb.ip_iters(constants)
# Add other empty slots
total_iters += sub_slot_iters * (len(header_block.finished_sub_slots) - 1)
else:
# Slot iters is guaranteed to be the same for header_block and prev_sb
# This takes the beginning of the slot, and adds ip_iters
total_iters = uint128(prev_sb.total_iters - prev_sb_iters)
total_iters = uint128(prev_sb.total_iters - prev_sb.ip_iters(constants))
total_iters += ip_iters
if total_iters != header_block.reward_chain_sub_block.total_iters:
return None, ValidationError(Err.INVALID_TOTAL_ITERS)
if new_sub_slot and not overflow:
# Start from start of this slot. Case of no overflow slots. Also includes genesis block after empty slot(s),
# but not overflowing
# Case 1: start from start of this slot. Case of no overflow slots. Also includes genesis block after empty
# slot(s), but not overflowing
rc_vdf_challenge: bytes32 = header_block.finished_sub_slots[-1].reward_chain.get_hash()
cc_vdf_challenge = header_block.finished_sub_slots[-1].challenge_chain.get_hash()
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
elif new_sub_slot and overflow and len(header_block.finished_sub_slots) > 1:
# Start from start of prev slot. Rare case of empty prev slot. Includes genesis block after 2 empty slots
# Case 2: start from start of prev slot. This is a rare case of empty prev slot. Includes genesis block after
# 2 empty slots
rc_vdf_challenge = header_block.finished_sub_slots[-2].reward_chain.get_hash()
cc_vdf_challenge = header_block.finished_sub_slots[-2].challenge_chain.get_hash()
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
elif genesis_block:
# Genesis block case, first challenge
# Case 3: Genesis block case, first challenge
rc_vdf_challenge = constants.FIRST_RC_CHALLENGE
cc_vdf_challenge = constants.FIRST_CC_CHALLENGE
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
else:
if new_sub_slot and overflow:
num_sub_slots_to_look_for = 1 # Starting at prev will skip 1 sub-slot
# Case 4: Starting at prev will put us in the previous, sub-slot, since case 2 handled more empty slots
num_sub_slots_to_look_for = 1
elif not new_sub_slot and overflow:
num_sub_slots_to_look_for = 2 # Starting at prev does not skip any sub slots
# Case 5: prev is in the same sub slot and also overflow. Starting at prev does not skip any sub slots
num_sub_slots_to_look_for = 2
elif not new_sub_slot and not overflow:
num_sub_slots_to_look_for = 1 # Starting at prev does not skip any sub slots, but we should not go back
# Case 6: prev is in the same sub slot. Starting at prev does not skip any sub slots. We do not need
# to go back another sub slot, because it's not overflow, so the VDF to signage point is this sub-slot.
num_sub_slots_to_look_for = 1
else:
assert False
sp_total_iters = total_iters - ip_iters + sp_iters
@ -463,6 +474,8 @@ async def validate_unfinished_header_block(
while num_sub_slots_to_look_for > 0:
if curr.first_in_sub_slot:
num_sub_slots_to_look_for -= 1
if num_sub_slots_to_look_for == 0:
break
if curr.total_iters < sp_total_iters:
break
if curr.height == 0:
@ -482,7 +495,7 @@ async def validate_unfinished_header_block(
curr = sub_blocks[curr.prev_hash]
cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1]
# 8. Check reward chain sp proof
# 9. Check reward chain sp proof
if sp_iters != 0:
target_vdf_info = VDFInfo(
rc_vdf_challenge,
@ -515,7 +528,7 @@ async def validate_unfinished_header_block(
curr = sub_blocks[curr.prev_hash]
rc_sp_hash = curr.finished_reward_slot_hashes[-1]
# 9. Check reward chain sp signature
# 10. Check reward chain sp signature
if not AugSchemeMPL.verify(
header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
rc_sp_hash,
@ -524,7 +537,7 @@ async def validate_unfinished_header_block(
log.error("block %s failed validation, rc sp sig validation %s, ", header_block.header_hash)
return None, ValidationError(Err.INVALID_RC_SIGNATURE)
# 10. Check cc sp
# 11. Check cc sp
if sp_iters != 0:
target_vdf_info = VDFInfo(
cc_vdf_challenge,
@ -546,7 +559,7 @@ async def validate_unfinished_header_block(
log.error("block %s failed validation, overflow should not include cc vdf, ", header_block.header_hash)
return None, ValidationError(Err.INVALID_CC_SP_VDF)
# 11. Check cc sp sig
# 12. Check cc sp sig
if not AugSchemeMPL.verify(
header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
cc_sp_hash,
@ -554,7 +567,7 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_CC_SIGNATURE, "invalid cc sp sig")
# 12. Check is_block
# 13. Check is_block
if genesis_block:
if header_block.foliage_sub_block.foliage_block_hash is None:
return None, ValidationError(Err.INVALID_IS_BLOCK, "invalid genesis")
@ -572,7 +585,7 @@ async def validate_unfinished_header_block(
if (our_sp_total_iters > curr.total_iters) != (header_block.foliage_sub_block.foliage_block_hash is not None):
return None, ValidationError(Err.INVALID_IS_BLOCK)
# 13. Check foliage sub block signature by plot key
# 14. Check foliage sub block signature by plot key
if not AugSchemeMPL.verify(
header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
header_block.foliage_sub_block.foliage_sub_block_data.get_hash(),
@ -580,7 +593,7 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_PLOT_SIGNATURE)
# 14. Check foliage block signature by plot key
# 15. Check foliage block signature by plot key
if header_block.foliage_sub_block.foliage_block_hash is not None:
if not AugSchemeMPL.verify(
header_block.reward_chain_sub_block.proof_of_space.plot_public_key,
@ -589,21 +602,21 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_PLOT_SIGNATURE)
# 15. Check unfinished reward chain sub block hash
# 16. Check unfinished reward chain sub block hash
if (
header_block.reward_chain_sub_block.get_hash()
!= header_block.foliage_sub_block.foliage_sub_block_data.unfinished_reward_block_hash
):
return None, ValidationError(Err.INVALID_URSB_HASH)
# 16. Check pool target max height
# 17. Check pool target max height
if (
header_block.foliage_sub_block.foliage_sub_block_data.pool_target.max_height != 0
and header_block.foliage_sub_block.foliage_sub_block_data.pool_target.max_height < height
):
return None, ValidationError(Err.OLD_POOL_TARGET)
# 17. Check pool target signature
# 18. Check pool target signature
if not AugSchemeMPL.verify(
header_block.reward_chain_sub_block.proof_of_space.pool_public_key,
bytes(header_block.foliage_sub_block.foliage_sub_block_data.pool_target),
@ -611,8 +624,8 @@ async def validate_unfinished_header_block(
):
return None, ValidationError(Err.INVALID_POOL_SIGNATURE)
# 18. Check extension data if applicable. None for mainnet.
# 19. Check if foliage block is present
# 19. Check extension data if applicable. None for mainnet.
# 20. Check if foliage block is present
if (header_block.foliage_sub_block.foliage_block_hash is not None) != (header_block.foliage_block is not None):
return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE)
@ -620,11 +633,11 @@ async def validate_unfinished_header_block(
return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE)
if header_block.foliage_block is not None:
# 20. Check foliage block hash
# 21. Check foliage block hash
if header_block.foliage_block.get_hash() != header_block.foliage_sub_block.foliage_block_hash:
return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH)
# 21. Check prev block hash
# 22. Check prev block hash
if genesis_block:
if header_block.foliage_block.prev_block_hash != bytes([0] * 32):
return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH)
@ -635,12 +648,12 @@ async def validate_unfinished_header_block(
if not header_block.foliage_block.prev_block_hash == curr_sb.header_hash:
return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH)
# 22. The filter hash in the Foliage Block must be the hash of the filter
# 23. The filter hash in the Foliage Block must be the hash of the filter
if check_filter:
if header_block.foliage_block.filter_hash != std_hash(header_block.transactions_filter):
return None, ValidationError(Err.INVALID_TRANSACTIONS_FILTER_HASH)
# 23. The timestamp in Foliage Block must comply with the timestamp rules
# 24. The timestamp in Foliage Block must comply with the timestamp rules
if prev_sb is not None:
last_timestamps: List[uint64] = []
curr_sb: SubBlockRecord = sub_blocks[header_block.foliage_block.prev_block_hash]
@ -697,14 +710,16 @@ async def validate_finished_header_block(
prev_sb: Optional[SubBlockRecord] = sub_blocks[header_block.prev_header_hash]
new_sub_slot: bool = len(header_block.finished_sub_slots) > 0
ips, difficulty = get_ips_and_difficulty(constants, unfinished_header_block, height_to_hash, prev_sb, sub_blocks)
ip_iters: uint64 = calculate_ip_iters(constants, ips, required_iters)
ip_iters: uint64 = calculate_ip_iters(
constants, ips, header_block.reward_chain_sub_block.signage_point_index, required_iters
)
if not genesis_block:
# 24. Check sub-block height
# 25. Check sub-block height
if header_block.height != prev_sb.height + 1:
return None, ValidationError(Err.INVALID_HEIGHT)
# 25. Check weight
# 26. Check weight
if header_block.weight != prev_sb.weight + difficulty:
return None, ValidationError(Err.INVALID_WEIGHT)
else:
@ -734,7 +749,7 @@ async def validate_finished_header_block(
ip_vdf_iters: uint64 = uint64(header_block.reward_chain_sub_block.total_iters - prev_sb.total_iters)
cc_vdf_output = prev_sb.challenge_vdf_output
# 26. Check challenge chain infusion point VDF
# 27. Check challenge chain infusion point VDF
if new_sub_slot:
cc_vdf_challenge = header_block.finished_sub_slots[-1].challenge_chain.get_hash()
else:
@ -762,7 +777,7 @@ async def validate_finished_header_block(
):
return None, ValidationError(Err.INVALID_CC_IP_VDF)
# 27. Check reward chain infusion point VDF
# 28. Check reward chain infusion point VDF
rc_target_vdf_info = VDFInfo(
rc_vdf_challenge,
ClassgroupElement.get_default_element(),
@ -776,9 +791,9 @@ async def validate_finished_header_block(
):
return None, ValidationError(Err.INVALID_RC_IP_VDF)
# 28. Check infused challenge chain infusion point VDF
# 29. Check infused challenge chain infusion point VDF
if not genesis_block:
overflow = is_overflow_sub_block(constants, ips, required_iters)
overflow = is_overflow_sub_block(constants, header_block.reward_chain_sub_block.signage_point_index)
deficit = calculate_deficit(
constants, header_block.height, prev_sb, overflow, len(header_block.finished_sub_slots) > 0
)
@ -835,11 +850,11 @@ async def validate_finished_header_block(
if header_block.infused_challenge_chain_ip_proof is not None:
return None, ValidationError(Err.INVALID_ICC_VDF)
# 29. Check reward block hash
# 30. Check reward block hash
if header_block.foliage_sub_block.reward_block_hash != header_block.reward_chain_sub_block.get_hash():
return None, ValidationError(Err.INVALID_REWARD_BLOCK_HASH)
# 30. Check reward block is_block
# 31. Check reward block is_block
if (header_block.foliage_sub_block.foliage_block_hash is not None) != header_block.reward_chain_sub_block.is_block:
return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE)

View File

@ -6,7 +6,6 @@ import multiprocessing
from typing import Dict, List, Optional, Tuple
from src.consensus.constants import ConsensusConstants
from src.consensus.pot_iterations import calculate_ip_iters, calculate_sp_iters
from src.full_node.block_body_validation import validate_block_body
from src.full_node.block_store import BlockStore
from src.full_node.coin_store import CoinStore
@ -20,7 +19,7 @@ from src.full_node.sub_block_record import SubBlockRecord
from src.types.sub_epoch_summary import SubEpochSummary
from src.types.unfinished_block import UnfinishedBlock
from src.util.errors import Err
from src.util.ints import uint32, uint64, uint128
from src.util.ints import uint32, uint64
from src.consensus.find_fork_point import find_fork_point_in_chain
from src.full_node.block_header_validation import (
validate_finished_header_block,
@ -296,9 +295,6 @@ class Blockchain:
def get_next_difficulty(self, header_hash: bytes32, new_slot: bool) -> uint64:
assert header_hash in self.sub_blocks
curr = self.sub_blocks[header_hash]
ip_iters = calculate_ip_iters(self.constants, curr.ips, curr.required_iters)
sp_iters = calculate_sp_iters(self.constants, curr.ips, curr.required_iters)
return get_next_difficulty(
self.constants,
self.sub_blocks,
@ -308,15 +304,12 @@ class Blockchain:
uint64(curr.weight - self.sub_blocks[curr.prev_hash].weight),
curr.deficit,
new_slot,
uint128(curr.total_iters - ip_iters + sp_iters),
curr.sp_total_iters(self.constants),
)
def get_next_slot_iters(self, header_hash: bytes32, new_slot: bool) -> uint64:
assert header_hash in self.sub_blocks
curr = self.sub_blocks[header_hash]
ip_iters = calculate_ip_iters(self.constants, curr.ips, curr.required_iters)
sp_iters = calculate_sp_iters(self.constants, curr.ips, curr.required_iters)
return (
get_next_ips(
self.constants,
@ -327,7 +320,7 @@ class Blockchain:
curr.ips,
curr.deficit,
new_slot,
uint128(curr.total_iters - ip_iters + sp_iters),
curr.sp_total_iters(self.constants),
)
* self.constants.SLOT_TIME_TARGET
)

View File

@ -1,4 +1,4 @@
from typing import Dict, List, Union
from typing import Dict, List, Union, Optional
from src.types.full_block import FullBlock
from src.types.header_block import HeaderBlock
@ -6,7 +6,6 @@ from src.types.unfinished_block import UnfinishedBlock
from src.types.unfinished_header_block import UnfinishedHeaderBlock
from src.consensus.constants import ConsensusConstants
from src.consensus.pot_iterations import calculate_sp_iters, calculate_ip_iters
from src.types.sized_bytes import bytes32
from src.full_node.sub_block_record import SubBlockRecord
from src.util.ints import uint32, uint64, uint128, uint8
@ -91,13 +90,9 @@ def _get_last_block_in_previous_epoch(
curr = fetched_blocks[fetched_index]
fetched_index += 1
last_sb_ip_iters = calculate_ip_iters(constants, last_sb_in_slot.ips, last_sb_in_slot.required_iters)
last_sb_sp_iters = calculate_sp_iters(constants, last_sb_in_slot.ips, last_sb_in_slot.required_iters)
prev_signage_point_total_iters = last_sb_in_slot.total_iters - last_sb_ip_iters + last_sb_sp_iters
# Backtrack to find the last block before the signage point
curr = sub_blocks[last_sb_in_slot.prev_hash]
while curr.total_iters > prev_signage_point_total_iters or not curr.is_block:
while curr.total_iters > last_sb_in_slot.sp_total_iters(constants) or not curr.is_block:
curr = sub_blocks[curr.prev_hash]
return curr
@ -109,7 +104,7 @@ def finishes_sub_epoch(
deficit: uint8,
also_finishes_epoch: bool,
sub_blocks: Dict[bytes32, SubBlockRecord],
prev_header_hash: bytes32,
prev_header_hash: Optional[bytes32],
) -> bool:
"""
Returns true if the next sub-slot after height will form part of a new sub-epoch (or epoch if also_finished_epoch
@ -119,6 +114,8 @@ def finishes_sub_epoch(
if height < constants.SUB_EPOCH_SUB_BLOCKS - 1:
return False
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
@ -192,6 +189,8 @@ def get_next_ips(
(last_block_curr.total_iters - last_block_prev.total_iters)
// (last_block_curr.timestamp - last_block_prev.timestamp)
)
new_ips_precise = new_ips_precise - (new_ips_precise % constants.NUM_SPS_SUB_SLOT) # Must divide the sub slot
new_ips = uint64(truncate_to_significant_bits(new_ips_precise, constants.SIGNIFICANT_BITS))
assert count_significant_bits(new_ips) <= constants.SIGNIFICANT_BITS
# Only change by a max factor as a sanity check
@ -210,7 +209,7 @@ def get_next_ips(
if new_ips >= last_block_curr.ips:
return min(new_ips, max_ips)
else:
return max([uint64(1), new_ips, min_ips])
return max([constants.NUM_SPS_SUB_SLOT, new_ips, min_ips])
def get_next_difficulty(

View File

@ -36,7 +36,7 @@ def full_block_to_sub_block_record(
len(block.finished_sub_slots) > 0,
prev_sb.total_iters,
)
overflow = is_overflow_sub_block(constants, ips, required_iters)
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)
prev_block_hash = block.foliage_block.prev_block_hash if block.foliage_block is not None else None
timestamp = block.foliage_block.timestamp if block.foliage_block is not None else None
@ -69,7 +69,7 @@ def full_block_to_sub_block_record(
constants,
sub_blocks,
block.height,
prev_sb,
sub_blocks[prev_sb.prev_hash],
block.finished_sub_slots[0].challenge_chain.new_difficulty,
block.finished_sub_slots[0].challenge_chain.new_ips,
)
@ -92,6 +92,7 @@ def full_block_to_sub_block_record(
block.height,
block.weight,
block.total_iters,
block.reward_chain_sub_block.signage_point_index,
block.reward_chain_sub_block.challenge_chain_ip_vdf.output,
icc_output,
block.reward_chain_sub_block.get_hash(),
@ -101,6 +102,7 @@ def full_block_to_sub_block_record(
block.foliage_sub_block.foliage_sub_block_data.farmer_reward_puzzle_hash,
required_iters,
deficit,
overflow,
timestamp,
prev_block_hash,
finished_challenge_slot_hashes,

View File

@ -166,7 +166,12 @@ class FullNode:
difficulty = self.blockchain.get_next_difficulty(peak, False)
if peak is not None:
ses: Optional[SubEpochSummary] = next_sub_epoch_summary(
self.constants, self.blockchain.sub_blocks, self.blockchain.height_to_hash, peak.ips, peak_block
self.constants,
self.blockchain.sub_blocks,
self.blockchain.height_to_hash,
peak.signage_point_index,
peak.ips,
peak_block,
)
timelord_new_peak: timelord_protocol.NewPeak = timelord_protocol.NewPeak(
peak_block.reward_chain_sub_block, difficulty, peak.deficit, peak.ips, ses
@ -638,10 +643,10 @@ class FullNode:
curr = await self.blockchain.get_full_block(curr.prev_header_hash)
assert curr is not None
peak_sub_slot = curr.finished_sub_slots[-1]
peak_sub_slot_iters = new_peak.total_iters - calculate_ip_iters(
self.constants, new_peak.ips, new_peak.required_iters
peak_sub_slot_iters = new_peak.infusion_sub_slot_total_iters(self.constants)
is_overflow: bool = is_overflow_sub_block(
self.constants, sub_block.reward_chain_sub_block.signage_point_index
)
is_overflow: bool = is_overflow_sub_block(self.constants, new_peak.ips, new_peak.required_iters)
if is_overflow:
# Find the previous sub-slots end of slot
if len(curr.finished_sub_slots) >= 2:
@ -801,10 +806,7 @@ class FullNode:
peak: Optional[SubBlockRecord] = self.blockchain.get_peak()
if peak is not None:
peak_sp = calculate_sp_iters(self.constants, peak.ips, peak.required_iters)
peak_ip_iters = calculate_ip_iters(self.constants, peak.ips, peak.required_iters)
sp_iters = peak.total_iters - (peak_ip_iters - peak_sp)
if block.total_iters < sp_iters:
if block.total_iters < peak.sp_total_iters(self.constants):
# This means this unfinished block is pretty far behind, it will not add weight to our chain
return
@ -846,7 +848,12 @@ class FullNode:
block.reward_chain_sp_proof,
block.foliage_sub_block,
next_sub_epoch_summary(
self.constants, self.blockchain.sub_blocks, self.blockchain.height_to_hash, required_iters, block
self.constants,
self.blockchain.sub_blocks,
self.blockchain.height_to_hash,
block.reward_chain_sub_block.signage_point_index,
required_iters,
block,
),
)
@ -1012,9 +1019,7 @@ class FullNode:
# 3. In a future sub-slot that we already know of
# Checks that the proof of space is valid
quality_string: Optional[bytes32] = request.proof_of_space.verify_and_get_quality_string(
self.constants, request.challenge_chain_sp, request.challenge_chain_sp_signature
)
quality_string: Optional[bytes32] = request.proof_of_space.verify_and_get_quality_string(self.constants)
assert len(quality_string) == 32
# Grab best transactions from Mempool for given tip target
@ -1041,9 +1046,10 @@ class FullNode:
quality_string,
request.proof_of_space.size,
difficulty,
request.challenge_chain_sp,
)
sp_iters: uint64 = calculate_sp_iters(self.constants, ips, required_iters)
ip_iters: uint64 = calculate_ip_iters(self.constants, ips, required_iters)
sp_iters: uint64 = calculate_sp_iters(self.constants, ips, request.signage_point_index)
ip_iters: uint64 = calculate_ip_iters(self.constants, ips, request.signage_point_index, required_iters)
total_iters_pos_slot: uint128 = pos_sub_slot[2]
def get_plot_sig(to_sign, _) -> G2Element:
@ -1060,6 +1066,7 @@ class FullNode:
unfinished_block: Optional[UnfinishedBlock] = create_unfinished_block(
self.constants,
total_iters_pos_slot,
request.signage_point_index,
sp_iters,
ip_iters,
request.proof_of_space,
@ -1068,13 +1075,13 @@ class FullNode:
request.pool_target,
get_plot_sig,
get_plot_sig,
sp_vdfs,
uint64(int(time.time())),
b"",
spend_bundle,
peak,
self.blockchain.sub_blocks,
finished_sub_slots,
sp_vdfs,
)
self.full_node_store.add_candidate_block(quality_string, unfinished_block)
@ -1153,16 +1160,7 @@ class FullNode:
ips, difficulty = get_ips_and_difficulty(
self.constants, unfinished_block, self.blockchain.height_to_hash, prev_sb, self.blockchain.sub_blocks
)
q_str: Optional[bytes32] = unfinished_block.reward_chain_sub_block.proof_of_space.verify_and_get_quality_string(
self.constants,
)
assert q_str is not None # We have previously validated this
required_iters: uint64 = calculate_iterations_quality(
q_str,
unfinished_block.reward_chain_sub_block.proof_of_space.size,
difficulty,
)
overflow = is_overflow_sub_block(self.constants, ips, required_iters)
overflow = is_overflow_sub_block(self.constants, unfinished_block.reward_chain_sub_block.signage_point_index)
if overflow:
finished_sub_slots = self.full_node_store.get_finished_sub_slots(
prev_sb,

View File

@ -247,7 +247,7 @@ class FullNodeStore:
sub_slot_iters = calculate_sub_slot_iters(self.constants, peak.ips)
# If we don't have this slot, return False
assert 0 < index < self.constants.NUM_CHECKPOINTS_PER_SLOT
assert 0 < index < self.constants.NUM_SPS_SUB_SLOT
for sub_slot, sp_dict, start_ss_total_iters in self.finished_sub_slots:
if sub_slot is None and start_ss_total_iters == 0:
ss_challenge_hash = self.constants.FIRST_CC_CHALLENGE
@ -259,11 +259,11 @@ class FullNodeStore:
# If we do have this slot, find the Prev sub-block from SP and validate SP
if peak is not None and start_ss_total_iters > peak.total_iters:
# We are in a future sub slot from the peak, so maybe there is a new IPS
checkpoint_size: uint64 = uint64(next_sub_slot_iters // self.constants.NUM_CHECKPOINTS_PER_SLOT)
checkpoint_size: uint64 = uint64(next_sub_slot_iters // self.constants.NUM_SPS_SUB_SLOT)
delta_iters = checkpoint_size * index
else:
# We are not in a future sub slot from the peak, so there is no new IPS
checkpoint_size: uint64 = uint64(sub_slot_iters // self.constants.NUM_CHECKPOINTS_PER_SLOT)
checkpoint_size: uint64 = uint64(sub_slot_iters // self.constants.NUM_SPS_SUB_SLOT)
delta_iters = checkpoint_size * index
sp_total_iters = start_ss_total_iters + delta_iters
@ -330,7 +330,7 @@ class FullNodeStore:
return SignagePoint(None, None, None, None)
for _, sps_at_index in sps.items():
for sp in sps_at_index:
if sp.cc_vdf.get_hash() == cc_signage_point:
if sp.cc_vdf.output.get_hash() == cc_signage_point:
return sp
return None

View File

@ -16,7 +16,7 @@ def make_sub_epoch_summary(
constants: ConsensusConstants,
sub_blocks: Dict[bytes32, SubBlockRecord],
blocks_included_height: uint32,
prev_sb: SubBlockRecord,
prev_prev_sub_block: bytes32,
new_difficulty: Optional[uint64],
new_ips: Optional[uint64],
) -> SubEpochSummary:
@ -25,11 +25,11 @@ def make_sub_epoch_summary(
"blocks_included_height". Prev_sb is the last sub block in the previous sub-epoch. On a new epoch,
new_difficulty and new_ips are also added.
"""
assert prev_sb.height == blocks_included_height - 1
assert prev_prev_sub_block.height == blocks_included_height - 2
if blocks_included_height // constants.SUB_EPOCH_SUB_BLOCKS == 1:
ses = SubEpochSummary(constants.GENESIS_SES_HASH, constants.FIRST_RC_CHALLENGE, uint8(0), None, None)
else:
curr = prev_sb
curr = 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
@ -37,7 +37,7 @@ def make_sub_epoch_summary(
ses = SubEpochSummary(
prev_ses,
curr.finished_reward_slot_hashes[-1],
curr.height % constants.SUB_EPOCH_SUB_BLOCKS,
uint8(curr.height % constants.SUB_EPOCH_SUB_BLOCKS),
new_difficulty,
new_ips,
)
@ -49,6 +49,7 @@ def next_sub_epoch_summary(
constants: ConsensusConstants,
sub_blocks: Dict[bytes32, SubBlockRecord],
height_to_hash: Dict[uint32, bytes32],
signage_point_index: uint8,
required_iters: uint64,
block: Union[UnfinishedBlock, FullBlock],
) -> Optional[SubEpochSummary]:
@ -57,8 +58,6 @@ def next_sub_epoch_summary(
ips = constants.IPS_STARTING
else:
assert prev_sb is not None
prev_ip_iters = calculate_ip_iters(constants, prev_sb.ips, prev_sb.required_iters)
prev_sp_iters = calculate_sp_iters(constants, prev_sb.ips, prev_sb.required_iters)
ips = get_next_ips(
constants,
sub_blocks,
@ -68,18 +67,22 @@ def next_sub_epoch_summary(
prev_sb.ips,
prev_sb.deficit,
len(block.finished_sub_slots) > 0,
uint128(block.total_iters - prev_ip_iters + prev_sp_iters),
prev_sb.sp_total_iters(constants),
)
overflow = is_overflow_sub_block(constants, ips, required_iters)
overflow = is_overflow_sub_block(constants, signage_point_index)
deficit = calculate_deficit(constants, block.height, prev_sb, overflow, len(block.finished_sub_slots) > 0)
finishes_se = finishes_sub_epoch(constants, block.height, deficit, False, sub_blocks, prev_sb.header_hash)
finishes_epoch: bool = finishes_sub_epoch(constants, block.height, deficit, True, sub_blocks, prev_sb.header_hash)
finishes_se = finishes_sub_epoch(
constants, block.height, deficit, False, sub_blocks, prev_sb.header_hash if prev_sb is not None else None
)
finishes_epoch: bool = finishes_sub_epoch(
constants, block.height, deficit, True, sub_blocks, prev_sb.header_hash if prev_sb is not None else None
)
if finishes_se:
assert prev_sb is not None
if finishes_epoch:
ip_iters = calculate_ip_iters(constants, ips, required_iters)
sp_iters = calculate_sp_iters(constants, ips, required_iters)
sp_iters = calculate_sp_iters(constants, ips, signage_point_index)
ip_iters = calculate_ip_iters(constants, ips, signage_point_index, required_iters)
next_difficulty = get_next_difficulty(
constants,
sub_blocks,
@ -105,5 +108,5 @@ def next_sub_epoch_summary(
else:
next_difficulty = None
next_ips = None
return make_sub_epoch_summary(constants, sub_blocks, block.height, prev_sb, next_difficulty, next_ips)
return make_sub_epoch_summary(constants, sub_blocks, block.height + 1, prev_sb, next_difficulty, next_ips)
return None

View File

@ -2,7 +2,7 @@ from dataclasses import dataclass
from typing import Optional, List
from src.consensus.constants import ConsensusConstants
from src.types.header_block import HeaderBlock
from src.consensus.pot_iterations import calculate_sp_iters, calculate_ip_iters, calculate_sub_slot_iters
from src.types.sub_epoch_summary import SubEpochSummary
from src.util.ints import uint8, uint32, uint64, uint128
from src.types.sized_bytes import bytes32
@ -24,6 +24,7 @@ class SubBlockRecord(Streamable):
sub_block_height: uint32
weight: uint128 # Total cumulative difficulty of all ancestor blocks since genesis
total_iters: uint128 # Total number of VDF iterations since genesis, including this sub-block
signage_point_index: uint8
challenge_vdf_output: ClassgroupElement # This is the intermediary VDF output at ip_iters in challenge chain
infused_challenge_vdf_output: Optional[
ClassgroupElement
@ -35,6 +36,7 @@ class SubBlockRecord(Streamable):
farmer_puzzle_hash: bytes32
required_iters: uint64 # The number of iters required for this proof of space
deficit: uint8 # A deficit of 5 is an overflow block after an infusion. Deficit of 4 is a challenge block
overflow: bool
# Block (present iff is_block)
timestamp: Optional[uint64]
@ -49,16 +51,34 @@ class SubBlockRecord(Streamable):
sub_epoch_summary_included: Optional[SubEpochSummary]
@property
def height(self):
def height(self) -> uint32:
return self.sub_block_height
@property
def is_block(self):
def is_block(self) -> bool:
return self.timestamp is not None
@property
def first_in_sub_slot(self):
def first_in_sub_slot(self) -> bool:
return self.finished_challenge_slot_hashes is not None
def is_challenge_sub_block(self, constants: ConsensusConstants):
def is_challenge_sub_block(self, constants: ConsensusConstants) -> bool:
return self.deficit == constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1
def pos_sub_slot_total_iters(self, constants: ConsensusConstants) -> uint128:
if self.overflow:
return uint128(self.total_iters - self.ip_iters(constants) - calculate_sub_slot_iters(constants, self.ips))
else:
return uint128(self.total_iters - self.ip_iters(constants))
def infusion_sub_slot_total_iters(self, constants: ConsensusConstants) -> uint128:
return self.total_iters - self.ip_iters(constants)
def sp_iters(self, constants: ConsensusConstants) -> uint64:
return calculate_sp_iters(constants, self.ips, self.signage_point_index)
def ip_iters(self, constants: ConsensusConstants) -> uint64:
return calculate_ip_iters(constants, self.ips, self.signage_point_index, self.required_iters)
def sp_total_iters(self, constants: ConsensusConstants):
return self.pos_sub_slot_total_iters(constants) + self.sp_iters(constants)

View File

@ -11,7 +11,7 @@ from blspy import G1Element, G2Element, AugSchemeMPL
from chiapos import DiskProver
from src.consensus.constants import ConsensusConstants
from src.consensus.pot_iterations import calculate_iterations_quality
from src.consensus.pot_iterations import calculate_iterations_quality, calculate_sp_interval_iters
from src.protocols import harvester_protocol
from src.server.connection import PeerConnections
from src.server.outbound_message import Delivery, Message, NodeType, OutboundMessage
@ -36,7 +36,8 @@ class Harvester:
no_key_filenames: Set[Path]
farmer_public_keys: List[G1Element]
pool_public_keys: List[G1Element]
cached_challenges: List[harvester_protocol.NewChallenge]
cached_signage_points: List[harvester_protocol.NewSignagePoint]
cached_qualities: Dict[bytes32, bytes32]
root_path: Path
_is_shutdown: bool
executor: ThreadPoolExecutor
@ -61,7 +62,8 @@ class Harvester:
self.state_changed_callback = None
self.server = None
self.constants = constants
self.cached_challenges = []
self.cached_signage_points = []
self.cached_qualities = {}
async def _start(self):
self._refresh_lock = asyncio.Lock()
@ -169,27 +171,29 @@ class Harvester:
log.warning("Not farming any plots on this harvester. Check your configuration.")
return
for new_challenge in self.cached_challenges:
async for msg in self.new_challenge(new_challenge):
for new_challenge in self.cached_signage_points:
async for msg in self.new_signage_point(new_challenge):
yield msg
self.cached_challenges = []
self.cached_signage_points = []
self._state_changed("plots")
@api_request
async def new_challenge(self, new_challenge: harvester_protocol.NewChallenge):
async def new_signage_point(self, new_challenge: harvester_protocol.NewSignagePoint):
"""
The harvester receives a new challenge from the farmer, this happens at the start of each slot.
The harvester receives a new signage point from the farmer, this happens at the start of each slot.
The harvester does a few things:
1. Checks the plot filter to see which plots can qualify for this slot (1/16 on average)
2. Looks up the quality for these plots, approximately 7 reads per plot which qualifies. Note that each plot
may have 0, 1, 2, etc qualities for that challenge: but on average it will have 1.
3. Checks the required_iters for each quality to see which are eligible for inclusion (< sub_slot_iters)
4. Looks up the full proof of space in the plot for each quality, approximately 64 reads per quality
5. Returns the proof of space to the farmer
1. If it's the first time seeing this challenge, clears the old challenges, and gets the qualities for each plot
This is approximately 7 reads per plot which qualifies. Note that each plot
may have 0, 1, 2, etc qualities for that challenge: but on average it will have 1. If it's not the first time,
the qualities are fetched from the cache.
2. Checks the required_iters for each quality and the given signage point, to see which are eligible for
inclusion (required_iters < sp_interval_iters).
3. Looks up the full proof of space in the plot for each quality, approximately 64 reads per quality
4. Returns the proof of space to the farmer
"""
if len(self.pool_public_keys) == 0 or len(self.farmer_public_keys) == 0:
self.cached_challenges = self.cached_challenges[:5]
self.cached_challenges.insert(0, new_challenge)
self.cached_signage_points = self.cached_signage_points[:5]
self.cached_signage_points.insert(0, new_challenge)
return
start = time.time()
@ -200,26 +204,36 @@ class Harvester:
loop = asyncio.get_running_loop()
# Remove old things from cache
if len(self.cached_qualities) != 0:
for key in self.cached_qualities.keys():
if key != new_challenge.challenge_hash:
del self.cached_qualities[key]
lookup_qualities = new_challenge.challenge_hash in self.cached_qualities
def blocking_lookup(filename: Path, plot_info: PlotInfo, prover: DiskProver) -> List[ProofOfSpace]:
# Uses the DiskProver object to lookup qualities. This is a blocking call,
# so it should be run in a thread pool.
try:
quality_strings = prover.get_qualities_for_challenge(new_challenge.challenge_hash)
except Exception:
log.error("Error using prover object. Reinitializing prover object.")
self.provers[filename] = dataclasses.replace(plot_info, prover=DiskProver(str(filename)))
return []
if lookup_qualities:
try:
quality_strings = prover.get_qualities_for_challenge(new_challenge.challenge_hash)
except Exception:
log.error("Error using prover object. Reinitializing prover object.")
self.provers[filename] = dataclasses.replace(plot_info, prover=DiskProver(str(filename)))
return []
else:
quality_strings = self.cached_qualities[new_challenge.challenge_hash]
responses: List[ProofOfSpace] = []
if quality_strings is not None:
# Found proofs of space (on average 1 is expected per plot)
for index, quality_str in enumerate(quality_strings):
required_iters: uint64 = calculate_iterations_quality(
quality_str,
prover.get_size(),
new_challenge.difficulty,
quality_str, prover.get_size(), new_challenge.difficulty, new_challenge.challenge_hash
)
if required_iters < new_challenge.slot_iterations:
sp_interval_iters = calculate_sp_interval_iters(self.constants, new_challenge.ips)
if required_iters < sp_interval_iters:
# Found a very good proof of space! will fetch the whole proof from disk, then send to farmer
try:
proof_xs = prover.get_full_proof(new_challenge.challenge_hash, index)
@ -234,6 +248,7 @@ class Harvester:
ProofOfSpace(
new_challenge.challenge_hash,
plot_info.pool_public_key,
None,
plot_public_key,
uint8(prover.get_size()),
proof_xs,
@ -241,15 +256,17 @@ class Harvester:
)
return responses
async def lookup_challenge(filename: Path, prover: DiskProver) -> List[harvester_protocol.ChallengeResponse]:
async def lookup_challenge(filename: Path, prover: DiskProver) -> List[harvester_protocol.NewProofOfSpace]:
# Executes a DiskProverLookup in a thread pool, and returns responses
all_responses: List[harvester_protocol.ChallengeResponse] = []
all_responses: List[harvester_protocol.NewProofOfSpace] = []
proofs_of_space: List[ProofOfSpace] = await loop.run_in_executor(
self.executor, blocking_lookup, filename, prover
)
for proof_of_space in proofs_of_space:
all_responses.append(
harvester_protocol.ChallengeResponse(prover.get_id(), new_challenge.challenge_hash, proof_of_space)
harvester_protocol.NewProofOfSpace(
prover.get_id(), proof_of_space, new_challenge.signage_point_index
)
)
return all_responses
@ -258,7 +275,7 @@ class Harvester:
if filename.exists():
# Passes the plot filter (does not check sp filter yet though, since we have not reached sp)
# This is being executed at the beginning of the slot
if ProofOfSpace.can_create_proof(
if ProofOfSpace.passes_plot_filter(
self.constants,
plot_info.prover.get_id(),
new_challenge.challenge_hash,

View File

@ -20,14 +20,15 @@ class NewSignagePoint:
challenge_chain_sp: bytes32
reward_chain_sp: bytes32
difficulty: uint64
ips: uint64
signage_point_index: uint8
slot_iterations: uint64
@dataclass(frozen=True)
@cbor_message
class DeclareProofOfSpace:
challenge_chain_sp: bytes32
signage_point_index: uint8
reward_chain_sp: bytes32
proof_of_space: ProofOfSpace
challenge_chain_sp_signature: G2Element

View File

@ -6,7 +6,7 @@ from blspy import G1Element, G2Element
from src.types.proof_of_space import ProofOfSpace
from src.types.sized_bytes import bytes32
from src.util.cbor_message import cbor_message
from src.util.ints import uint64
from src.util.ints import uint64, uint8
"""
Protocol between harvester and farmer.
@ -22,17 +22,20 @@ class HarvesterHandshake:
@dataclass(frozen=True)
@cbor_message
class NewChallenge:
class NewSignagePoint:
challenge_hash: bytes32
difficulty: uint64
slot_iterations: uint64
ips: uint64
signage_point_index: uint8
sp_hash: bytes32
@dataclass(frozen=True)
@cbor_message
class ChallengeResponse:
class NewProofOfSpace:
plot_identifier: str
proof: ProofOfSpace
signage_point_index: uint8
@dataclass(frozen=True)

View File

@ -4,6 +4,7 @@ import logging
import time
from typing import Dict, List, Optional, Tuple
from blspy import G2Element
from chiavdf import create_discriminant
from src.consensus.constants import ConsensusConstants
@ -18,7 +19,7 @@ from src.server.outbound_message import OutboundMessage, NodeType, Message, Deli
from src.server.server import ChiaServer
from src.types.classgroup import ClassgroupElement
from src.types.end_of_slot_bundle import EndOfSubSlotBundle
from src.types.proof_of_space import ProofOfSpace
from src.types.reward_chain_sub_block import RewardChainSubBlock
from src.types.sized_bytes import bytes32
from src.types.slots import ChallengeChainSubSlot, InfusedChallengeChainSubSlot, RewardChainSubSlot, SubSlotProofs
from src.types.vdf import VDFInfo, VDFProof
@ -28,16 +29,27 @@ from src.util.ints import uint64, uint8, int512
log = logging.getLogger(__name__)
def iters_from_proof_of_space(constants, pos: ProofOfSpace, ips, difficulty) -> Tuple[uint64, uint64]:
quality = pos.verify_and_get_quality_string()
def iters_from_sub_block(
constants,
reward_chain_sub_block: RewardChainSubBlock,
ips: uint64,
difficulty: uint64,
) -> Tuple[uint64, uint64]:
quality = reward_chain_sub_block.proof_of_space.verify_and_get_quality_string()
if reward_chain_sub_block.challenge_chain_sp_vdf is None:
assert reward_chain_sub_block.signage_point_index == 0
cc_sp: bytes32 = reward_chain_sub_block.proof_of_space.challenge_hash
else:
cc_sp: bytes32 = reward_chain_sub_block.challenge_chain_sp_vdf.get_hash()
required_iters = calculate_iterations_quality(
quality,
pos.size,
reward_chain_sub_block.proof_of_space.size,
difficulty,
cc_sp,
)
return (
calculate_sp_iters(constants, ips, required_iters),
calculate_ip_iters(constants, ips, required_iters),
calculate_sp_iters(constants, reward_chain_sub_block.signage_point_index),
calculate_ip_iters(constants, ips, reward_chain_sub_block.signage_point_index, required_iters),
)
@ -52,6 +64,7 @@ class Timelord:
self.ip_whitelist = self.config["vdf_clients"]["ip"]
self.server: Optional[ChiaServer] = None
self.chain_type_to_stream: Dict[str, Tuple[ip, asyncio.StreamReader, asyncio.StreamWriter]] = {}
# Chains that currently don't have a vdf_client.
self.unspawned_chains: List[str] = ["cc", "rc", "icc"]
# Chains that currently accept iterations.
@ -71,7 +84,7 @@ class Timelord:
# For each iteration submitted, know if it's a signage point, an infusion point or an end of slot.
self.iteration_to_proof_type: Dict[uint64, str] = {}
# List of proofs finished.
self.proofs_finished: List[Tuple[str, VDFInfo, VDFProof]] = {}
self.proofs_finished: List[Tuple[str, VDFInfo, VDFProof]] = []
# Data to send at vdf_client initialization.
self.discriminant_to_submit: Dict[str, bytes32] = {}
self.initial_form_to_submit: Dict[str, ClassgroupElement] = {}
@ -79,7 +92,7 @@ class Timelord:
self.new_subslot = False
self.finished_sp = 0
self.cached_peak = None
self.overflow_blocks: List[Tuple[timelord_protocol.NewUnfinishedSubBlock]] = []
self.overflow_blocks: List[timelord_protocol.NewUnfinishedSubBlock] = []
def _set_server(self, server: ChiaServer):
self.server = server
@ -93,8 +106,8 @@ class Timelord:
@api_request
async def new_unfinished_subblock(self, new_unfinished_subblock: timelord_protocol.NewUnfinishedSubBlock):
async with self.lock:
sp_iters, ip_iters = iters_from_proof_of_space(
new_unfinished_subblock.reward_chain_sub_block.proof_of_space,
sp_iters, ip_iters = iters_from_sub_block(
new_unfinished_subblock.reward_chain_sub_block,
self.cached_peak.ips,
self.cached_peak.difficulty,
)
@ -103,7 +116,7 @@ class Timelord:
elif ip_iters > self.last_ip_iters:
self.unfinished_blocks.append(new_unfinished_subblock)
for chain in ["cc", "icc", "rc"]:
self.iters_to_submit[chain] = uint64(ip_iters - self.last_ip_iters)
self.iters_to_submit[chain].append(uint64(ip_iters - self.last_ip_iters))
self.iteration_to_proof_type[ip_iters - self.last_ip_iters] = "ip"
async def _handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
@ -151,8 +164,11 @@ class Timelord:
self.initial_form_to_submit["rc"] = ClassgroupElement.get_default_element()
# Retrieve the iterations of this peak.
if not self.new_subslot:
_, ip_iters = iters_from_proof_of_space(
self.constants, self.sub_block.proof_of_space, self.last_peak.ips, self.last_peak.difficulty
_, ip_iters = iters_from_sub_block(
self.constants,
self.last_peak.reward_chain_sub_block.proof_of_space,
self.last_peak.ips,
self.last_peak.difficulty,
)
else:
ip_iters = 0
@ -160,16 +176,16 @@ class Timelord:
sub_slot_iters = calculate_sub_slot_iters(self.constants, self.last_peak.ips)
# Adjust all signage points iterations to the peak.
iters_per_signage = uint64(sub_slot_iters // self.constants.NUM_CHECKPOINTS_PER_SLOT)
iters_per_signage = uint64(sub_slot_iters // self.constants.NUM_SPS_SUB_SLOT)
self.signage_point_iters = [
k * iters_per_signage - ip_iters
for k in range(1, self.constants.NUM_CHECKPOINTS_PER_SLOT + 1)
for k in range(1, self.constants.NUM_SPS_SUB_SLOT + 1)
if k * iters_per_signage - ip_iters > 0 and k * iters_per_signage <= sub_slot_iters
]
# Adjust all unfinished blocks iterations to the peak.
new_unfinished_blocks = []
for block in self.unfinished_blocks:
block_sp_iters, block_ip_iters = iters_from_proof_of_space(
block_sp_iters, block_ip_iters = iters_from_sub_block(
self.constants, block.proof_of_space, self.last_peak.ips, self.last_peak.difficulty
)
if block_sp_iters < block_ip_iters:
@ -184,7 +200,7 @@ class Timelord:
self.iteration_to_proof_type[new_block_iters] = "ip"
new_overflow_blocks = []
for block in self.overflow_blocks:
_, block_ip_iters = iters_from_proof_of_space(
_, block_ip_iters = iters_from_sub_block(
self.constants, block.proof_of_space, self.last_peak.ips, self.last_peak.difficulty
)
new_block_iters = block_ip_iters - ip_iters
@ -294,7 +310,7 @@ class Timelord:
Delivery.BROADCAST,
)
)
# Cleanup the signage point fromm memory.
# Cleanup the signage point from memory.
self.signage_point_iters.remove(signage_iters[0])
for chain in ["cc", "rc"]:
del self.iteration_to_proof_type[chain][signage_iters[0]]
@ -302,33 +318,35 @@ class Timelord:
self.proofs_finished = self._clear_proof_list(signage_iters[0])
# Send the next signage point to the chains.
if len(self.signage_point_iters) > 0:
next_sp = min(self.ignage_point_iters)
next_sp = min(self.signage_point_iters)
for chain in ["cc", "rc"]:
self.iters_to_submit[chain] = next_sp
self.iteration_to_proof_type[next_sp] = "sp"
async def _check_for_new_ip(self):
infusion_iters = [iter for iter, t in self.iteration_to_proof_type if t == "ip"]
for iter in infusion_iters:
for iteration in infusion_iters:
proofs_with_iter = [
(chain, info, proof) for chain, info, proof in self.proofs_finished if info.number_of_iterations == iter
(chain, info, proof)
for chain, info, proof in self.proofs_finished
if info.number_of_iterations == iteration
]
chain_count = 3 if self.has_icc else 2
if len(proofs_with_iter) == chain_count:
block = None
for unfinished_block in self.unfinished_blocks + self.overflow_blocks:
_, ip_iters = iters_from_proof_of_space(
_, ip_iters = iters_from_sub_block(
self.constants, self.cached_peak.ips, self.cached_peak.difficulty
)
if ip_iters - self.last_ip_iters == iter:
if ip_iters - self.last_ip_iters == iteration:
block = unfinished_block
break
if block is not None:
self.unfinished_blocks.remove(block)
for chain in ["cc", "rc"]:
del self.iteration_to_proof_type[chain][iter]
del self.iteration_to_proof_type[chain][iteration]
if self.has_icc:
del self.iteration_to_proof_type["icc"][iter]
del self.iteration_to_proof_type["icc"][iteration]
challenge_hash = block.reward_chain_sub_block.get_hash()
icc_info = None
icc_proof = None
@ -358,8 +376,8 @@ class Timelord:
Delivery.BROADCAST,
)
)
for iter in infusion_iters:
self.proofs_finished = self._clear_proof_list(iter)
for iteration in infusion_iters:
self.proofs_finished = self._clear_proof_list(iteration)
async def _check_for_end_of_subslot(self):
chains_finished = [
@ -424,7 +442,7 @@ class Timelord:
# Calculate overflow blocks for the next subslot.
self.overflow_blocks = []
for unfinished_block in self.unfinished_blocks:
sp_iters, ip_iters = iters_from_proof_of_space(
sp_iters, ip_iters = iters_from_sub_block(
self.constants, self.cached_peak.ips, self.cached_peak.difficulty
)
if sp_iters > ip_iters:

View File

@ -5,8 +5,10 @@ from bitstring import BitArray
from blspy import G1Element, G2Element
from chiapos import Verifier
from src.consensus.pos_quality import quality_str_to_quality
from src.types.sized_bytes import bytes32
from src.util.ints import uint8
from src.util.ints import uint8, uint64
from src.util.streamable import Streamable, streamable
from src.util.hash import std_hash
from src.consensus.constants import ConsensusConstants
@ -28,16 +30,10 @@ class ProofOfSpace(Streamable):
return self.calculate_plot_id_ph(self.pool_contract_puzzle_hash, self.plot_public_key)
return self.calculate_plot_id_pk(self.pool_public_key, self.plot_public_key)
def verify_and_get_quality_string(
self,
constants: ConsensusConstants,
sp_output_hash: Optional[bytes32] = None,
sp_signature: Optional[G2Element] = None,
) -> Optional[bytes32]:
def verify_and_get_quality_string(self, constants: ConsensusConstants) -> Optional[bytes32]:
v: Verifier = Verifier()
plot_id: bytes32 = self.get_plot_id()
if not self.can_create_proof(constants, plot_id, self.challenge_hash, sp_output_hash, sp_signature):
if not ProofOfSpace.passes_plot_filter(constants, plot_id, self.challenge_hash):
return None
quality_str = v.validate_proof(plot_id, self.size, self.challenge_hash, bytes(self.proof))
@ -48,23 +44,9 @@ class ProofOfSpace(Streamable):
return quality_str
@staticmethod
def can_create_proof(
constants: ConsensusConstants,
plot_id: bytes32,
challenge_hash: bytes32,
sp_output_hash: Optional[bytes32] = None,
sp_signature: Optional[G2Element] = None,
) -> bool:
# If sp_output is provided, both plot filters are checked. Otherwise only the first one is checked
def passes_plot_filter(constants: ConsensusConstants, plot_id: bytes32, challenge_hash: bytes32) -> bool:
plot_filter = BitArray(std_hash(plot_id + challenge_hash))
if sp_output_hash is None:
return plot_filter[: constants.NUMBER_ZERO_BITS_PLOT_FILTER].uint == 0
else:
sp_filter = BitArray(std_hash(plot_id + sp_output_hash + bytes(sp_signature)))
return (
plot_filter[: constants.NUMBER_ZERO_BITS_PLOT_FILTER].uint == 0
and sp_filter[: constants.NUMBER_ZERO_BITS_SP_FILTER].uint == 0
)
return plot_filter[: constants.NUMBER_ZERO_BITS_PLOT_FILTER].uint == 0
@staticmethod
def calculate_plot_id_pk(

View File

@ -2,7 +2,7 @@ from dataclasses import dataclass
from blspy import G2Element
from typing import Optional
from src.util.ints import uint32, uint128
from src.util.ints import uint32, uint128, uint8
from src.util.streamable import Streamable, streamable
from src.types.proof_of_space import ProofOfSpace
from src.types.vdf import VDFInfo
@ -12,6 +12,7 @@ from src.types.vdf import VDFInfo
@streamable
class RewardChainSubBlockUnfinished(Streamable):
total_iters: uint128
signage_point_index: uint8
proof_of_space: ProofOfSpace
challenge_chain_sp_vdf: Optional[VDFInfo] # Not present for first sp in slot
challenge_chain_sp_signature: G2Element
@ -25,6 +26,7 @@ class RewardChainSubBlock(Streamable):
weight: uint128
sub_block_height: uint32
total_iters: uint128
signage_point_index: uint8
proof_of_space: ProofOfSpace
challenge_chain_sp_vdf: Optional[VDFInfo] # Not present for first sp in slot
challenge_chain_sp_signature: G2Element
@ -38,6 +40,7 @@ class RewardChainSubBlock(Streamable):
def get_unfinished(self) -> RewardChainSubBlockUnfinished:
return RewardChainSubBlockUnfinished(
self.total_iters,
self.signage_point_index,
self.proof_of_space,
self.challenge_chain_sp_vdf,
self.challenge_chain_sp_signature,

View File

@ -23,14 +23,12 @@ from src.consensus.pot_iterations import (
calculate_iterations_quality,
calculate_sp_iters,
calculate_sub_slot_iters,
)
from src.full_node.difficulty_adjustment import (
get_next_difficulty,
get_next_ips,
finishes_sub_epoch,
calculate_sp_interval_iters,
is_overflow_sub_block,
)
from src.full_node.full_block_to_sub_block_record import full_block_to_sub_block_record
from src.full_node.make_sub_epoch_summary import make_sub_epoch_summary
from src.full_node.make_sub_epoch_summary import next_sub_epoch_summary
from src.full_node.signage_point import SignagePoint
from src.full_node.sub_block_record import SubBlockRecord
from src.plotting.plot_tools import load_plots, PlotInfo
from src.types.classgroup import ClassgroupElement
@ -231,6 +229,7 @@ class BlockTools:
height_to_hash, difficulty, sub_blocks = load_block_list(block_list, constants)
latest_sub_block: SubBlockRecord = sub_blocks[block_list[-1].header_hash]
print(f"Last sub block {latest_sub_block.height} {latest_sub_block.total_iters}")
curr = latest_sub_block
while not curr.is_block:
curr = sub_blocks[curr.prev_hash]
@ -238,25 +237,23 @@ class BlockTools:
start_height = curr.height
curr = latest_sub_block
sub_blocks_added_this_sub_slot = 1
while not curr.first_in_sub_slot:
curr = sub_blocks[curr.prev_hash]
sub_blocks_added_this_sub_slot += 1
finished_sub_slots: List[EndOfSubSlotBundle] = [] # Sub-slots since last sub block
finished_sub_slots_at_sp: List[EndOfSubSlotBundle] = [] # Sub-slots since last sub block, up to signage point
finished_sub_slots_at_ip: List[EndOfSubSlotBundle] = [] # Sub-slots since last sub block, up to infusion point
ips: uint64 = latest_sub_block.ips
sub_slot_iters: uint64 = calculate_sub_slot_iters(constants, ips) # The number of iterations in one sub-slot
same_slot_as_last = True # Only applies to first slot, to prevent old blocks from being added
sub_slot_start_total_iters: uint128 = latest_sub_block.total_iters - calculate_ip_iters(
constants, latest_sub_block.ips, latest_sub_block.required_iters
)
sub_slot_start_total_iters: uint128 = latest_sub_block.infusion_sub_slot_total_iters(constants)
# Start at the last block in block list
# Get the challenge for that slot
while True:
print(
"Sub_slot_start_total_iters",
)
slot_cc_challenge, slot_rc_challenge = get_challenges(
sub_blocks, finished_sub_slots, latest_sub_block.header_hash
sub_blocks, finished_sub_slots_at_sp, latest_sub_block.header_hash
)
# If did not reach the target slots to skip, don't make any proofs for this sub-slot
@ -265,72 +262,108 @@ class BlockTools:
proofs_of_space = []
else:
# Get all proofs of space for challenge.
proofs_of_space: List[Tuple[uint64, ProofOfSpace]] = self.get_pospaces_for_challenge(
proofs_of_space: List[Tuple[str, ProofOfSpace]] = self.get_pospaces_for_challenge(
constants,
slot_cc_challenge,
seed,
difficulty,
ips,
)
overflow_pos = []
prev_num_of_blocks = num_blocks
for required_iters, proof_of_space in sorted(proofs_of_space, key=lambda t: t[0]):
if same_slot_as_last and required_iters <= latest_sub_block.required_iters:
# Ignore this sub-block because it's in the past
continue
sp_iters: uint64 = calculate_sp_iters(constants, ips, required_iters)
ip_iters = calculate_ip_iters(constants, ips, required_iters)
is_overflow_block = sp_iters > ip_iters
if force_overflow and not is_overflow_block:
continue
if is_overflow_block:
overflow_pos.append((required_iters, proof_of_space))
continue
assert latest_sub_block.header_hash in sub_blocks
if transaction_data_included:
transaction_data = None
full_block, sub_block_record = get_full_block_and_sub_record(
for signage_point_index in range(0, constants.NUM_SPS_SUB_SLOT - constants.NUM_SP_INTERVALS_EXTRA):
curr = latest_sub_block
while curr.total_iters > sub_slot_start_total_iters + calculate_sp_iters(
constants, ips, uint8(signage_point_index)
):
if curr.height == 0:
break
curr = sub_blocks[curr.prev_hash]
if curr.total_iters > sub_slot_start_total_iters:
finished_sub_slots_at_sp = []
signage_point: SignagePoint = get_signage_point(
constants,
sub_slot_start_total_iters,
proof_of_space,
slot_cc_challenge,
slot_rc_challenge,
farmer_reward_puzzle_hash,
pool_target,
start_timestamp,
start_height,
time_per_sub_block,
transaction_data,
height_to_hash,
difficulty,
required_iters,
ips,
self.get_plot_signature,
self.get_pool_key_signature,
finished_sub_slots,
seed,
latest_sub_block,
sub_blocks,
latest_sub_block,
sub_slot_start_total_iters,
uint8(signage_point_index),
finished_sub_slots_at_sp,
ips,
)
qualified_proofs: List[Tuple[uint64, ProofOfSpace]] = []
for quality_str, proof_of_space in proofs_of_space:
if signage_point_index == 0:
cc_sp_output_hash: bytes32 = slot_cc_challenge
else:
assert signage_point is not None
cc_sp_output_hash: bytes32 = signage_point.cc_vdf.output.get_hash()
required_iters = calculate_iterations_quality(
quality_str,
proof_of_space.size,
difficulty,
cc_sp_output_hash,
)
if required_iters < calculate_sp_interval_iters(constants, ips):
qualified_proofs.append((required_iters, proof_of_space))
if full_block is None:
continue
if sub_block_record.is_block:
transaction_data_included = True
for required_iters, proof_of_space in sorted(qualified_proofs, key=lambda t: t[0]):
if sub_blocks_added_this_sub_slot == constants.MAX_SLOT_SUB_BLOCKS: # or force_overflow:
break
if same_slot_as_last:
if signage_point_index < latest_sub_block.signage_point_index:
# Ignore this sub-block because it's in the past
continue
if signage_point_index == latest_sub_block.signage_point_index:
# Ignore this sub-block because it's in the past
if required_iters <= latest_sub_block.required_iters:
continue
assert latest_sub_block.header_hash in sub_blocks
if transaction_data_included:
transaction_data = None
full_block, sub_block_record = get_full_block_and_sub_record(
constants,
sub_slot_start_total_iters,
uint8(signage_point_index),
proof_of_space,
slot_cc_challenge,
slot_rc_challenge,
farmer_reward_puzzle_hash,
pool_target,
start_timestamp,
start_height,
time_per_sub_block,
transaction_data,
height_to_hash,
difficulty,
required_iters,
ips,
self.get_plot_signature,
self.get_pool_key_signature,
finished_sub_slots_at_ip,
signage_point,
seed,
latest_sub_block,
sub_blocks,
)
block_list.append(full_block)
num_blocks -= 1
if num_blocks == 0:
return block_list
print(f"Block tools: created block {full_block.height} {full_block.total_iters}, not overflow")
sub_blocks[full_block.header_hash] = sub_block_record
height_to_hash[uint32(full_block.height)] = full_block.header_hash
latest_sub_block = sub_blocks[full_block.header_hash]
finished_sub_slots = []
if full_block is None:
continue
if sub_block_record.is_block:
transaction_data_included = True
block_list.append(full_block)
sub_blocks_added_this_sub_slot += 1
num_blocks -= 1
if num_blocks == 0:
return block_list
sub_blocks[full_block.header_hash] = sub_block_record
print(f"Added block {sub_block_record.height} overflow=False, iters {sub_block_record.total_iters}")
height_to_hash[uint32(full_block.height)] = full_block.header_hash
latest_sub_block = sub_blocks[full_block.header_hash]
finished_sub_slots_at_ip = []
# Finish the end of sub-slot and try again next sub-slot
# End of sub-slot logic
if len(finished_sub_slots) == 0:
if len(finished_sub_slots_at_ip) == 0:
# Sub block has been created within this sub-slot
eos_iters = sub_slot_iters - (latest_sub_block.total_iters - sub_slot_start_total_iters)
cc_input = latest_sub_block.challenge_vdf_output
@ -362,7 +395,7 @@ class BlockTools:
icc_ip_vdf, icc_ip_proof = get_icc(
constants,
sub_slot_start_total_iters + sub_slot_iters,
finished_sub_slots,
finished_sub_slots_at_ip,
latest_sub_block,
sub_blocks,
sub_slot_start_total_iters,
@ -374,28 +407,32 @@ class BlockTools:
cc_vdf.challenge_hash, ClassgroupElement.get_default_element(), sub_slot_iters, cc_vdf.output
)
sub_epoch_summary: Optional[SubEpochSummary] = handle_end_of_sub_epoch(
sub_epoch_summary: Optional[SubEpochSummary] = next_sub_epoch_summary(
constants,
latest_sub_block,
sub_blocks,
height_to_hash,
latest_sub_block.signage_point_index,
latest_sub_block.required_iters,
block_list[-1],
)
if sub_epoch_summary is not None:
ses_hash = sub_epoch_summary.get_hash()
new_ips: Optional[uint64] = sub_epoch_summary.new_ips
new_difficulty: Optional[uint64] = sub_epoch_summary.new_difficulty
print(f"Block tools: sub epoch summary: {sub_epoch_summary}")
if new_ips is not None:
ips = new_ips
difficulty = new_difficulty
overflow_pos = [] # No overflow blocks on new difficulty
print("Sub epoch summary:", sub_epoch_summary)
else:
ses_hash = None
new_ips = None
new_difficulty = None
if icc_ip_vdf is not None:
if len(finished_sub_slots) == 0:
# Icc vdf (Deficit of latest sub-block is <= 4)
if len(finished_sub_slots_at_ip) == 0:
# This means there are sub-blocks in this sub-slot
curr = latest_sub_block
while not curr.is_challenge_sub_block(constants) and not curr.first_in_sub_slot:
curr = sub_blocks[curr.prev_hash]
@ -404,6 +441,7 @@ class BlockTools:
else:
icc_eos_iters = sub_slot_iters
else:
# This means there are no sub-blocks in this sub-slot
icc_eos_iters = sub_slot_iters
icc_ip_vdf = VDFInfo(
icc_ip_vdf.challenge_hash,
@ -415,10 +453,11 @@ class BlockTools:
icc_sub_slot_hash = icc_sub_slot.get_hash() if latest_sub_block.deficit == 0 else None
cc_sub_slot = ChallengeChainSubSlot(cc_vdf, icc_sub_slot_hash, ses_hash, new_ips, new_difficulty)
else:
# No icc
icc_sub_slot = None
cc_sub_slot = ChallengeChainSubSlot(cc_vdf, None, ses_hash, new_ips, new_difficulty)
finished_sub_slots.append(
finished_sub_slots_at_ip.append(
EndOfSubSlotBundle(
cc_sub_slot,
icc_sub_slot,
@ -431,54 +470,103 @@ class BlockTools:
SubSlotProofs(cc_proof, icc_ip_proof, rc_proof),
)
)
overflow_cc_challenge = finished_sub_slots[-1].challenge_chain.get_hash()
overflow_rc_challenge = finished_sub_slots[-1].reward_chain.get_hash()
finished_sub_slots_eos = finished_sub_slots_at_ip.copy()
overflow_cc_challenge = finished_sub_slots_at_ip[-1].challenge_chain.get_hash()
overflow_rc_challenge = finished_sub_slots_at_ip[-1].reward_chain.get_hash()
if transaction_data_included:
transaction_data = None
for required_iters, proof_of_space in overflow_pos:
full_block, sub_block_record = get_full_block_and_sub_record(
constants,
sub_slot_start_total_iters,
proof_of_space,
slot_cc_challenge,
slot_rc_challenge,
farmer_reward_puzzle_hash,
pool_target,
start_timestamp,
start_height,
time_per_sub_block,
transaction_data,
height_to_hash,
difficulty,
required_iters,
ips,
self.get_plot_signature,
self.get_pool_key_signature,
finished_sub_slots,
seed,
latest_sub_block,
sub_blocks,
overflow_cc_challenge=overflow_cc_challenge,
overflow_rc_challenge=overflow_rc_challenge,
)
sub_blocks_added_this_sub_slot = 0 # Sub slot ended, overflows are in next sub slot
if full_block is None:
continue
if sub_block_record.is_block:
transaction_data_included = True
if new_ips is None:
# No overflows on new epoch
for signage_point_index in range(
constants.NUM_SPS_SUB_SLOT - constants.NUM_SP_INTERVALS_EXTRA, constants.NUM_SPS_SUB_SLOT
):
curr = latest_sub_block
while curr.total_iters > sub_slot_start_total_iters + calculate_sp_iters(
constants, ips, uint8(signage_point_index)
):
if curr.height == 0:
break
curr = sub_blocks[curr.prev_hash]
if curr.total_iters > sub_slot_start_total_iters:
finished_sub_slots_at_sp = []
signage_point: SignagePoint = get_signage_point(
constants,
sub_blocks,
latest_sub_block,
sub_slot_start_total_iters,
uint8(signage_point_index),
finished_sub_slots_at_sp,
ips,
)
qualified_proofs: List[Tuple[uint64, ProofOfSpace]] = []
for quality_str, proof_of_space in proofs_of_space:
if signage_point_index == 0:
cc_sp_output_hash: bytes32 = slot_cc_challenge
else:
assert signage_point is not None
cc_sp_output_hash: bytes32 = signage_point.cc_vdf.output.get_hash()
required_iters = calculate_iterations_quality(
quality_str,
proof_of_space.size,
difficulty,
cc_sp_output_hash,
)
if required_iters < calculate_sp_interval_iters(constants, ips):
qualified_proofs.append((required_iters, proof_of_space))
for required_iters, proof_of_space in sorted(qualified_proofs, key=lambda t: t[0]):
if sub_blocks_added_this_sub_slot == constants.MAX_SLOT_SUB_BLOCKS:
break
full_block, sub_block_record = get_full_block_and_sub_record(
constants,
sub_slot_start_total_iters,
uint8(signage_point_index),
proof_of_space,
slot_cc_challenge,
slot_rc_challenge,
farmer_reward_puzzle_hash,
pool_target,
start_timestamp,
start_height,
time_per_sub_block,
transaction_data,
height_to_hash,
difficulty,
required_iters,
ips,
self.get_plot_signature,
self.get_pool_key_signature,
finished_sub_slots_at_ip,
signage_point,
seed,
latest_sub_block,
sub_blocks,
overflow_cc_challenge=overflow_cc_challenge,
overflow_rc_challenge=overflow_rc_challenge,
)
block_list.append(full_block)
num_blocks -= 1
if num_blocks == 0:
return block_list
print(f"Block tools: created block {full_block.height} {full_block.total_iters}, overflow")
if full_block is None:
continue
if sub_block_record.is_block:
transaction_data_included = True
sub_blocks[full_block.header_hash] = sub_block_record
height_to_hash[uint32(full_block.height)] = full_block.header_hash
latest_sub_block = sub_blocks[full_block.header_hash]
finished_sub_slots = []
block_list.append(full_block)
sub_blocks_added_this_sub_slot += 1
print(
f"Added block {sub_block_record.height } overflow=True, iters {sub_block_record.total_iters}"
)
num_blocks -= 1
if num_blocks == 0:
return block_list
sub_blocks[full_block.header_hash] = sub_block_record
height_to_hash[uint32(full_block.height)] = full_block.header_hash
latest_sub_block = sub_blocks[full_block.header_hash]
finished_sub_slots_at_ip = []
finished_sub_slots_at_sp = finished_sub_slots_eos.copy()
same_slot_as_last = False
sub_slot_start_total_iters += sub_slot_iters
sub_slot_iters = calculate_sub_slot_iters(constants, ips)
@ -508,68 +596,95 @@ class BlockTools:
# Keep trying until we get a good proof of space that also passes sp filter
while True:
cc_challenge, rc_challenge = get_genesis_challenges(constants, finished_sub_slots)
proofs_of_space: List[Tuple[uint64, ProofOfSpace]] = self.get_pospaces_for_challenge(
proofs_of_space: List[Tuple[str, ProofOfSpace]] = self.get_pospaces_for_challenge(
constants,
cc_challenge,
seed,
uint64(constants.DIFFICULTY_STARTING),
uint64(constants.IPS_STARTING),
)
# Try each of the proofs of space
for required_iters, proof_of_space in sorted(proofs_of_space, key=lambda t: t[0]):
sp_iters: uint64 = calculate_sp_iters(constants, uint64(constants.IPS_STARTING), required_iters)
ip_iters = calculate_ip_iters(constants, uint64(constants.IPS_STARTING), required_iters)
is_overflow_block = sp_iters > ip_iters
if force_overflow and not is_overflow_block:
continue
if len(finished_sub_slots) < skip_slots:
continue
unfinished_block = create_unfinished_block(
for signage_point_index in range(0, constants.NUM_SPS_SUB_SLOT):
signage_point: SignagePoint = get_signage_point(
constants,
{},
None,
sub_slot_total_iters,
sp_iters,
ip_iters,
proof_of_space,
cc_challenge,
farmer_reward_puzzle_hash,
PoolTarget(constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH, uint32(0)),
self.get_plot_signature,
self.get_pool_key_signature,
timestamp,
seed,
finished_sub_slots=finished_sub_slots,
uint8(signage_point_index),
finished_sub_slots,
constants.IPS_STARTING,
)
if unfinished_block is None:
continue
if not is_overflow_block:
cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof(
constants,
ClassgroupElement.get_default_element(),
cc_challenge,
ip_iters,
qualified_proofs: List[Tuple[uint64, ProofOfSpace]] = []
for quality_str, proof_of_space in proofs_of_space:
if signage_point_index == 0:
cc_sp_output_hash: bytes32 = cc_challenge
else:
assert signage_point is not None
cc_sp_output_hash: bytes32 = signage_point.cc_vdf.output.get_hash()
required_iters = calculate_iterations_quality(
quality_str,
proof_of_space.size,
constants.DIFFICULTY_STARTING,
cc_sp_output_hash,
)
rc_ip_vdf, rc_ip_proof = get_vdf_info_and_proof(
if required_iters < calculate_sp_interval_iters(constants, constants.IPS_STARTING):
qualified_proofs.append((required_iters, proof_of_space))
# Try each of the proofs of space
for required_iters, proof_of_space in qualified_proofs:
sp_iters: uint64 = calculate_sp_iters(
constants, uint64(constants.IPS_STARTING), uint8(signage_point_index)
)
ip_iters = calculate_ip_iters(
constants, uint64(constants.IPS_STARTING), uint8(signage_point_index), required_iters
)
is_overflow_block = is_overflow_sub_block(constants, uint8(signage_point_index))
if force_overflow and not is_overflow_block:
continue
if len(finished_sub_slots) < skip_slots:
continue
unfinished_block = create_unfinished_block(
constants,
ClassgroupElement.get_default_element(),
rc_challenge,
sub_slot_total_iters,
uint8(signage_point_index),
sp_iters,
ip_iters,
proof_of_space,
cc_challenge,
farmer_reward_puzzle_hash,
PoolTarget(constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH, uint32(0)),
self.get_plot_signature,
self.get_pool_key_signature,
signage_point,
timestamp,
seed,
finished_sub_slots=finished_sub_slots,
)
assert unfinished_block is not None
return unfinished_block_to_full_block(
unfinished_block,
cc_ip_vdf,
cc_ip_proof,
rc_ip_vdf,
rc_ip_proof,
None,
None,
finished_sub_slots,
None,
constants.DIFFICULTY_STARTING,
)
if not is_overflow_block:
cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof(
constants,
ClassgroupElement.get_default_element(),
cc_challenge,
ip_iters,
)
rc_ip_vdf, rc_ip_proof = get_vdf_info_and_proof(
constants,
ClassgroupElement.get_default_element(),
rc_challenge,
ip_iters,
)
assert unfinished_block is not None
return unfinished_block_to_full_block(
unfinished_block,
cc_ip_vdf,
cc_ip_proof,
rc_ip_vdf,
rc_ip_proof,
None,
None,
finished_sub_slots,
None,
constants.DIFFICULTY_STARTING,
)
# Finish the end of sub-slot and try again next sub-slot
cc_vdf, cc_proof = get_vdf_info_and_proof(
@ -626,8 +741,8 @@ class BlockTools:
sub_slot_total_iters += sub_slot_iters
def get_pospaces_for_challenge(
self, constants: ConsensusConstants, challenge_hash: bytes32, seed: bytes, difficulty: uint64, ips: uint64
) -> (ProofOfSpace, uint64):
self, constants: ConsensusConstants, challenge_hash: bytes32, seed: bytes
) -> List[Tuple[str, ProofOfSpace]]:
found_proofs: List[(uint64, ProofOfSpace)] = []
plots: List[PlotInfo] = [
plot_info for _, plot_info in sorted(list(self.plots.items()), key=lambda x: str(x[0]))
@ -638,32 +753,24 @@ class BlockTools:
for plot_info in plots:
# Allow passing in seed, to create reorgs and different chains
plot_id = plot_info.prover.get_id()
if ProofOfSpace.can_create_proof(constants, plot_id, challenge_hash, None, None):
if ProofOfSpace.passes_plot_filter(constants, plot_id, challenge_hash):
passed_plot_filter += 1
qualities = plot_info.prover.get_qualities_for_challenge(challenge_hash)
for proof_index, quality_str in enumerate(qualities):
sub_slot_iters = calculate_sub_slot_iters(constants, ips)
required_iters: uint64 = calculate_iterations_quality(
quality_str,
plot_info.prover.get_size(),
difficulty,
proof_xs: bytes = plot_info.prover.get_full_proof(challenge_hash, proof_index)
plot_pk = ProofOfSpace.generate_plot_public_key(
plot_info.local_sk.get_g1(),
plot_info.farmer_public_key,
)
if required_iters < sub_slot_iters:
proof_xs: bytes = plot_info.prover.get_full_proof(challenge_hash, proof_index)
plot_pk = ProofOfSpace.generate_plot_public_key(
plot_info.local_sk.get_g1(),
plot_info.farmer_public_key,
)
proof_of_space: ProofOfSpace = ProofOfSpace(
challenge_hash,
plot_info.pool_public_key,
None,
plot_pk,
plot_info.prover.get_size(),
proof_xs,
)
found_proofs.append((required_iters, proof_of_space))
proof_of_space: ProofOfSpace = ProofOfSpace(
challenge_hash,
plot_info.pool_public_key,
None,
plot_pk,
plot_info.prover.get_size(),
proof_xs,
)
found_proofs.append((quality_str, proof_of_space))
if len(found_proofs) >= 4:
# Removes some proofs of space to create "random" chains, based on the seed
random_sample = random.sample(found_proofs, len(found_proofs) - 3)
@ -676,12 +783,108 @@ class BlockTools:
return random_sample
def get_signage_point(
constants: ConsensusConstants,
sub_blocks: Dict[bytes32, SubBlockRecord],
latest_sub_block: Optional[SubBlockRecord],
sub_slot_start_total_iters: uint128,
signage_point_index: uint8,
finished_sub_slots: List[EndOfSubSlotBundle],
ips: uint64,
) -> SignagePoint:
if signage_point_index == 0:
return SignagePoint(None, None, None, None)
is_genesis = latest_sub_block is None
sp_iters = calculate_sp_iters(constants, ips, signage_point_index)
overflow = is_overflow_sub_block(constants, signage_point_index)
total_iters_sp: uint128 = sub_slot_start_total_iters + calculate_sp_iters(constants, ips, signage_point_index)
new_sub_slot = len(finished_sub_slots) > 0
if is_genesis:
cc_vdf_input = ClassgroupElement.get_default_element()
if len(finished_sub_slots) == 0:
cc_vdf_challenge = constants.FIRST_CC_CHALLENGE
rc_vdf_challenge = constants.FIRST_RC_CHALLENGE
else:
cc_vdf_challenge = finished_sub_slots[-1].challenge_chain.get_hash()
rc_vdf_challenge = finished_sub_slots[-1].reward_chain.get_hash()
sp_vdf_iters = sp_iters
elif new_sub_slot and not overflow:
# Start from start of this slot. Case of no overflow slots. Also includes genesis block after
# empty slot (but not overflowing)
rc_vdf_challenge: bytes32 = finished_sub_slots[-1].reward_chain.get_hash()
cc_vdf_challenge = finished_sub_slots[-1].challenge_chain.get_hash()
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
elif new_sub_slot and overflow and len(finished_sub_slots) > 1:
# Start from start of prev slot. Rare case of empty prev slot.
# Includes genesis block after 2 empty slots
rc_vdf_challenge = finished_sub_slots[-2].reward_chain.get_hash()
cc_vdf_challenge = finished_sub_slots[-2].challenge_chain.get_hash()
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
else:
if new_sub_slot and overflow:
num_sub_slots_to_look_for = 1 # Starting at prev will skip 1 sub-slot
elif not new_sub_slot and overflow:
num_sub_slots_to_look_for = 2 # Starting at prev does not skip any sub slots
elif not new_sub_slot and not overflow:
num_sub_slots_to_look_for = 1 # Starting at prev does not skip any sub slots, but we should not go back
else:
assert False
next_sb: SubBlockRecord = latest_sub_block
curr: SubBlockRecord = next_sb
# Finds a sub-block which is BEFORE our signage point, otherwise goes back to the end of sub-slot
# Note that for overflow sub-blocks, we are looking at the end of the previous sub-slot
while num_sub_slots_to_look_for > 0:
next_sb = curr
if curr.first_in_sub_slot:
num_sub_slots_to_look_for -= 1
if curr.total_iters < total_iters_sp:
break
if curr.height == 0:
break
curr = sub_blocks[curr.prev_hash]
if curr.total_iters < total_iters_sp:
sp_vdf_iters = total_iters_sp - curr.total_iters
cc_vdf_input = curr.challenge_vdf_output
rc_vdf_challenge = curr.reward_infusion_new_challenge
else:
sp_vdf_iters = sp_iters
cc_vdf_input = ClassgroupElement.get_default_element()
rc_vdf_challenge = next_sb.finished_reward_slot_hashes[-1]
while not curr.first_in_sub_slot:
curr = sub_blocks[curr.prev_hash]
cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1]
cc_sp_vdf, cc_sp_proof = get_vdf_info_and_proof(
constants,
cc_vdf_input,
cc_vdf_challenge,
sp_vdf_iters,
)
rc_sp_vdf, rc_sp_proof = get_vdf_info_and_proof(
constants,
ClassgroupElement.get_default_element(),
rc_vdf_challenge,
sp_vdf_iters,
)
return SignagePoint(cc_sp_vdf, cc_sp_proof, rc_sp_vdf, rc_sp_proof)
def finish_sub_block(
constants: ConsensusConstants,
sub_blocks: Dict[bytes32, SubBlockRecord],
height_to_hash: Dict[uint32, bytes32],
finished_sub_slots: List[EndOfSubSlotBundle],
sub_slot_start_total_iters: uint128,
signage_point_index: uint8,
unfinished_block: UnfinishedBlock,
required_iters: uint64,
ip_iters: uint64,
@ -691,7 +894,7 @@ def finish_sub_block(
ips: uint64,
difficulty: uint64,
):
is_overflow = required_iters > ip_iters
is_overflow = is_overflow_sub_block(constants, signage_point_index)
cc_vdf_challenge = slot_cc_challenge
slot_iters = calculate_sub_slot_iters(constants, ips)
if len(finished_sub_slots) == 0:
@ -786,7 +989,9 @@ def get_plot_dir():
return cache_path
def load_block_list(block_list, constants) -> (Dict[uint32, bytes32], uint64, Dict[uint32, SubBlockRecord]):
def load_block_list(
block_list: List[FullBlock], constants
) -> (Dict[uint32, bytes32], uint64, Dict[uint32, SubBlockRecord]):
difficulty = 0
height_to_hash: Dict[uint32, bytes32] = {}
sub_blocks: Dict[uint32, SubBlockRecord] = {}
@ -796,11 +1001,26 @@ def load_block_list(block_list, constants) -> (Dict[uint32, bytes32], uint64, Di
else:
difficulty = full_block.weight - block_list[full_block.height - 1].weight
quality_str = full_block.reward_chain_sub_block.proof_of_space.verify_and_get_quality_string(constants)
if full_block.reward_chain_sub_block.signage_point_index == 0:
if len(full_block.finished_sub_slots) > 0:
sp_hash = full_block.finished_sub_slots[-1].challenge_chain.get_hash()
else:
if full_block.height == 0:
sp_hash = constants.FIRST_CC_CHALLENGE
else:
curr = sub_blocks[full_block.prev_header_hash]
while not curr.first_in_sub_slot:
curr = sub_blocks[curr.prev_hash]
sp_hash = curr.finished_challenge_slot_hashes[-1]
else:
sp_hash = full_block.reward_chain_sub_block.challenge_chain_sp_vdf.output.get_hash()
required_iters: uint64 = calculate_iterations_quality(
quality_str,
full_block.reward_chain_sub_block.proof_of_space.size,
difficulty,
sp_hash,
)
sub_blocks[full_block.header_hash] = full_block_to_sub_block_record(
constants,
sub_blocks,
@ -856,8 +1076,6 @@ def get_icc(
else:
# First sub block in sub slot has deficit 0,1,2 or 3
icc_challenge_hash = curr.finished_infused_challenge_slot_hashes[-1]
if icc_input is None:
print(deficit, icc_input, latest_sub_block, finished_sub_slots)
return get_vdf_info_and_proof(
constants,
icc_input,
@ -866,60 +1084,10 @@ def get_icc(
)
def handle_end_of_sub_epoch(
constants: ConsensusConstants,
last_block: SubBlockRecord,
sub_blocks: Dict[bytes32, SubBlockRecord],
height_to_hash: Dict[uint32, bytes32],
) -> Optional[SubEpochSummary]:
fs = finishes_sub_epoch(constants, last_block.height, last_block.deficit, False, sub_blocks, last_block.prev_hash)
fe = finishes_sub_epoch(constants, last_block.height, last_block.deficit, True, sub_blocks, last_block.prev_hash)
if not fs: # Does not finish sub-epoch
return None
if not fe:
# Does not finish epoch
new_difficulty: Optional[uint64] = None
new_ips: Optional[uint64] = None
else:
ip_iters = calculate_ip_iters(constants, last_block.ips, last_block.required_iters)
sp_iters = calculate_sp_iters(constants, last_block.ips, last_block.required_iters)
new_difficulty = get_next_difficulty(
constants,
sub_blocks,
height_to_hash,
last_block.header_hash,
last_block.height,
uint64(last_block.weight - sub_blocks[last_block.prev_hash].weight),
last_block.deficit,
True,
uint128(last_block.total_iters - ip_iters + sp_iters),
)
new_ips = get_next_ips(
constants,
sub_blocks,
height_to_hash,
last_block.header_hash,
last_block.height,
last_block.ips,
last_block.deficit,
True,
uint128(last_block.total_iters - ip_iters + sp_iters),
)
return make_sub_epoch_summary(
constants,
sub_blocks,
last_block.height + 1,
last_block,
new_difficulty,
new_ips,
)
def get_full_block_and_sub_record(
constants: ConsensusConstants,
sub_slot_start_total_iters: uint128,
signage_point_index: uint8,
proof_of_space: ProofOfSpace,
slot_cc_challenge: bytes32,
slot_rc_challenge: bytes32,
@ -936,18 +1104,20 @@ def get_full_block_and_sub_record(
get_plot_signature: Callable[[bytes32, G1Element], G2Element],
get_pool_signature: Callable[[PoolTarget, G1Element], G2Element],
finished_sub_slots: List[EndOfSubSlotBundle],
signage_point: SignagePoint,
seed: bytes = b"",
prev_sub_block: Optional[SubBlockRecord] = None,
sub_blocks: Dict[uint32, SubBlockRecord] = None,
overflow_cc_challenge: bytes32 = None,
overflow_rc_challenge: bytes32 = None,
) -> Tuple[FullBlock, SubBlockRecord]:
sp_iters = calculate_sp_iters(constants, ips, required_iters)
ip_iters = calculate_ip_iters(constants, ips, required_iters)
sp_iters = calculate_sp_iters(constants, ips, signage_point_index)
ip_iters = calculate_ip_iters(constants, ips, signage_point_index, required_iters)
unfinished_block = create_unfinished_block(
constants,
sub_slot_start_total_iters,
signage_point_index,
sp_iters,
ip_iters,
proof_of_space,
@ -956,6 +1126,7 @@ def get_full_block_and_sub_record(
pool_target,
get_plot_signature,
get_pool_signature,
signage_point,
uint64(start_timestamp + int((prev_sub_block.height + 1 - start_height) * time_per_sub_block)),
seed,
transaction_data,
@ -963,8 +1134,6 @@ def get_full_block_and_sub_record(
sub_blocks,
finished_sub_slots,
)
if unfinished_block is None:
return None, None
if (overflow_cc_challenge is not None) and (overflow_rc_challenge is not None):
slot_cc_challenge = overflow_cc_challenge
@ -976,6 +1145,7 @@ def get_full_block_and_sub_record(
height_to_hash,
finished_sub_slots,
sub_slot_start_total_iters,
signage_point_index,
unfinished_block,
required_iters,
ip_iters,

View File

@ -124,6 +124,7 @@ class Err(Enum):
INVALID_ICC_HASH_CC = 98
INVALID_ICC_HASH_RC = 99
INVALID_ICC_EOS_VDF = 100
INVALID_SP_INDEX = 101
class ValidationError(Exception):

View File

@ -8,7 +8,7 @@ def truncate_to_significant_bits(input_x: int, num_significant_bits: int) -> int
if num_significant_bits > x.bit_length():
return x
lower = x.bit_length() - num_significant_bits
mask = (1 << (x.bit_length())) - 1 - ((1 << (lower)) - 1)
mask = (1 << (x.bit_length())) - 1 - ((1 << lower) - 1)
if input_x < 0:
return -(x & mask)
else:

View File

@ -13,8 +13,7 @@ from pytest import raises
test_constants = DEFAULT_CONSTANTS.replace(
**{
"EXTRA_ITERS_TIME_TARGET": 37.5,
"NUM_CHECKPOINTS_PER_SLOT": 32,
"NUM_SPS_SUB_SLOT": 32,
"SLOT_TIME_TARGET": 300,
}
)
@ -26,73 +25,63 @@ class TestPotIterations:
assert calculate_sub_slot_iters(test_constants, ips) == test_constants.SLOT_TIME_TARGET * ips
def test_is_overflow_sub_block(self):
ips: uint64 = uint64(100001)
assert not is_overflow_sub_block(test_constants, uint8(27))
assert not is_overflow_sub_block(test_constants, uint8(28))
assert is_overflow_sub_block(test_constants, uint8(29))
assert is_overflow_sub_block(test_constants, uint8(30))
assert is_overflow_sub_block(test_constants, uint8(31))
with raises(ValueError):
assert is_overflow_sub_block(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips + 1))
with raises(ValueError):
assert is_overflow_sub_block(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips))
assert is_overflow_sub_block(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips - 1))
assert is_overflow_sub_block(
constants,
ips,
uint64(test_constants.SLOT_TIME_TARGET * ips - int(test_constants.EXTRA_ITERS_TIME_TARGET * ips)),
)
assert not is_overflow_sub_block(
constants,
ips,
uint64(test_constants.SLOT_TIME_TARGET * ips - int(test_constants.EXTRA_ITERS_TIME_TARGET * ips) - 1),
)
assert not is_overflow_sub_block(constants, ips, uint64(0))
assert is_overflow_sub_block(test_constants, uint8(32))
def test_calculate_sp_iters(self):
ips: uint64 = uint64(100001)
with raises(ValueError):
calculate_sp_iters(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips + 1))
with raises(ValueError):
calculate_sp_iters(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips))
one_checkpoint_iters = test_constants.SLOT_TIME_TARGET * ips // 32
assert (
calculate_sp_iters(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips - 1))
== 31 * one_checkpoint_iters
)
assert calculate_sp_iters(test_constants, ips, uint64(20 * one_checkpoint_iters)) == 20 * one_checkpoint_iters
assert (
calculate_sp_iters(test_constants, ips, uint64(20 * one_checkpoint_iters) - 1) == 19 * one_checkpoint_iters
)
assert (
calculate_sp_iters(test_constants, ips, uint64(20 * one_checkpoint_iters) + 1) == 20 * one_checkpoint_iters
)
assert calculate_sp_iters(test_constants, ips, uint64(1)) == 0 * one_checkpoint_iters
assert calculate_sp_iters(test_constants, ips, uint64(0)) == 0 * one_checkpoint_iters
calculate_sp_iters(test_constants, ips, uint8(32))
calculate_sp_iters(test_constants, ips, uint8(31))
def test_calculate_ip_iters(self):
ips: uint64 = uint64(100001)
extra_iters = int(test_constants.EXTRA_ITERS_TIME_TARGET * int(ips))
one_checkpoint_iters = test_constants.SLOT_TIME_TARGET * ips // test_constants.NUM_CHECKPOINTS_PER_SLOT
with raises(ValueError):
calculate_ip_iters(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips + 1))
with raises(ValueError):
calculate_ip_iters(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips))
sp_interval_iters = test_constants.SLOT_TIME_TARGET * ips // test_constants.NUM_SPS_SUB_SLOT
assert calculate_ip_iters(test_constants, ips, uint64(test_constants.SLOT_TIME_TARGET * ips - 1)) == (
(test_constants.SLOT_TIME_TARGET * ips - 1) + extra_iters
) - (test_constants.SLOT_TIME_TARGET * ips)
assert (
calculate_ip_iters(constants, ips, uint64(5 * one_checkpoint_iters))
== 5 * one_checkpoint_iters + extra_iters
)
assert (
calculate_ip_iters(test_constants, ips, uint64(5 * one_checkpoint_iters + 678))
== 5 * one_checkpoint_iters + extra_iters + 678
)
assert (
calculate_ip_iters(test_constants, ips, uint64(5 * one_checkpoint_iters - 567))
== 5 * one_checkpoint_iters + extra_iters - 567
)
assert calculate_ip_iters(constants, ips, uint64(0)) == extra_iters
assert calculate_ip_iters(constants, ips, uint64(1)) == extra_iters + 1
with raises(ValueError):
# Invalid signage point index
calculate_ip_iters(test_constants, ips, uint8(123), uint64(100000))
sp_iters = sp_interval_iters * 13
with raises(ValueError):
# required_iters too high
calculate_ip_iters(test_constants, ips, sp_interval_iters, sp_interval_iters)
with raises(ValueError):
# required_iters too high
calculate_ip_iters(test_constants, ips, sp_interval_iters, sp_interval_iters * 12)
with raises(ValueError):
# required_iters too low (0)
calculate_ip_iters(test_constants, ips, sp_interval_iters, uint64(0))
required_iters = sp_interval_iters - 1
ip_iters = calculate_ip_iters(test_constants, ips, uint8(13), required_iters)
print(sp_iters, sp_interval_iters, required_iters)
assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters
required_iters = uint64(1)
ip_iters = calculate_ip_iters(test_constants, ips, uint8(13), required_iters)
assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters
required_iters = ips * 4
ip_iters = calculate_ip_iters(test_constants, ips, uint8(13), required_iters)
assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters
assert sp_iters < ip_iters
# Overflow
sp_iters = sp_interval_iters * (test_constants.NUM_SPS_SUB_SLOT - 1)
ip_iters = calculate_ip_iters(test_constants, ips, uint8(test_constants.NUM_SPS_SUB_SLOT - 1), required_iters)
assert ip_iters == (
sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters
) % calculate_sub_slot_iters(test_constants, ips)
assert sp_iters > ip_iters
def test_win_percentage(self):
"""
@ -100,8 +89,8 @@ class TestPotIterations:
with the assumption that all farmers have access to the same VDF speed.
"""
farmer_ks = {
uint8(32): 300,
uint8(33): 100,
uint8(32): 200,
uint8(33): 200,
uint8(34): 100,
uint8(35): 100,
uint8(36): 50,
@ -110,25 +99,27 @@ class TestPotIterations:
total_space = sum(farmer_space.values())
percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()}
wins = {k: 0 for k in farmer_ks.keys()}
total_slots = 500
sub_slot_iters = uint64(100000000)
total_slots = 200
num_sps = 16
sp_interval_iters = uint64(100000000 // 32)
difficulty = uint64(500000000000)
for slot_index in range(total_slots):
total_wins_in_slot = 0
for k, count in farmer_ks.items():
for farmer_index in range(count):
quality = std_hash(slot_index.to_bytes(32, "big") + bytes(farmer_index))
required_iters = calculate_iterations_quality(
quality,
k,
difficulty,
)
if required_iters < sub_slot_iters:
wins[k] += 1
total_wins_in_slot += 1
for sp_index in range(num_sps):
sp_hash = std_hash(slot_index.to_bytes(4, "big") + sp_index.to_bytes(4, "big"))
for k, count in farmer_ks.items():
for farmer_index in range(count):
quality = std_hash(slot_index.to_bytes(4, "big") + k.to_bytes(1, "big") + bytes(farmer_index))
required_iters = calculate_iterations_quality(quality, k, difficulty, sp_hash)
if required_iters < sp_interval_iters:
wins[k] += 1
total_wins_in_slot += 1
print(total_wins_in_slot)
win_percentage = {k: wins[k] / sum(wins.values()) for k in farmer_ks.keys()}
print(win_percentage)
print(percentage_space)
for k in farmer_ks.keys():
# Win rate is proportional to percentage of space
assert abs(win_percentage[k] - percentage_space[k]) < 0.01

View File

@ -94,7 +94,7 @@ class TestGenesisBlock:
class TestAddingMoreBlocks:
@pytest.mark.asyncio
async def test_basic_chain(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(test_constants, 200)
blocks = bt.get_consecutive_blocks(test_constants, 2000)
for block in blocks:
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
@ -138,7 +138,7 @@ class TestAddingMoreBlocks:
blocks = bt.get_consecutive_blocks(test_constants, 10, skip_slots=10, block_list=blocks)
for block in blocks[10:]:
result, err, _ = await blockchain.receive_block(block)
assert result == ReceiveBlockResult.NEW_PEAK
assert err is None
assert blockchain.get_peak().height == 19
@pytest.mark.asyncio
@ -181,6 +181,7 @@ class TestAddingMoreBlocks:
result, err, _ = await empty_blockchain.receive_block(block)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
print(f"added {block.height} {block.total_iters}")
assert empty_blockchain.get_peak().height == len(blocks) - 1
@pytest.mark.asyncio

View File

@ -27,11 +27,11 @@ from tests.time_out_assert import time_out_assert
test_constants = constants.replace(
**{
"DIFFICULTY_STARTING": 1,
"DIFFICULTY_STARTING": 2 ** 8,
"DISCRIMINANT_SIZE_BITS": 32,
"SUB_EPOCH_SUB_BLOCKS": 70,
"EPOCH_SUB_BLOCKS": 140,
"IPS_STARTING": 10 * 1,
"IPS_STARTING": 2 ** 9,
"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

View File

@ -2,7 +2,7 @@ from secrets import token_bytes
from blspy import AugSchemeMPL
from src.types.proof_of_space import ProofOfSpace # pylint: disable=E0401
# from src.consensus.default_constants import DEFAULT_CONSTANTS
from src.consensus.default_constants import DEFAULT_CONSTANTS
from src.types.classgroup import ClassgroupElement
@ -13,7 +13,7 @@ class TestProofOfSpace:
"""
num_trials = 40000
success_count = 0
target_filter = (2 ** constants.NUMBER_ZERO_BITS_PLOT_FILTER) * (2 ** constants.NUMBER_ZERO_BITS_SP_FILTER)
target_filter = (2 ** DEFAULT_CONSTANTS.NUMBER_ZERO_BITS_SP_FILTER) * (2 ** constants.NUMBER_ZERO_BITS_SP_FILTER)
sk = AugSchemeMPL.key_gen(bytes([0x44] * 32))
sig = AugSchemeMPL.sign(sk, b"")
for _ in range(num_trials):