Files
chia-blockchain/chia/consensus/block_creation.py
Amine KhaldiandGitHub 428e26cf5b Explore bringing back v2 plot pool keys support (#20388)
Explore bringing back v2 plot pool keys support.
2026-01-08 09:17:56 -08:00

518 lines
20 KiB
Python

from __future__ import annotations
import logging
import random
from collections.abc import Callable, Sequence
import chia_rs
from chia_rs import (
BlockRecord,
ConsensusConstants,
EndOfSubSlotBundle,
Foliage,
FoliageBlockData,
FoliageTransactionBlock,
FullBlock,
G1Element,
G2Element,
PoolTarget,
ProofOfSpace,
RewardChainBlock,
RewardChainBlockUnfinished,
TransactionsInfo,
UnfinishedBlock,
compute_merkle_set_root,
)
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint32, uint64, uint128
from chiabip158 import PyBIP158
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
from chia.consensus.blockchain_interface import BlockRecordsProtocol
from chia.consensus.coinbase import create_farmer_coin, create_pool_coin
from chia.consensus.prev_transaction_block import get_prev_transaction_block
from chia.consensus.signage_point import SignagePoint
from chia.types.blockchain_format.coin import Coin, hash_coin_ids
from chia.types.blockchain_format.vdf import VDFInfo, VDFProof
from chia.types.generator_types import NewBlockGenerator
from chia.util.hash import std_hash
log = logging.getLogger(__name__)
def compute_block_fee(additions: Sequence[Coin], removals: Sequence[Coin]) -> uint64:
removal_amount = 0
addition_amount = 0
for coin in removals:
removal_amount += coin.amount
for coin in additions:
addition_amount += coin.amount
return uint64(removal_amount - addition_amount)
def create_foliage(
constants: ConsensusConstants,
reward_block_unfinished: RewardChainBlockUnfinished,
new_block_gen: NewBlockGenerator | None,
prev_block: BlockRecord | None,
blocks: BlockRecordsProtocol,
total_iters_sp: uint128,
timestamp: uint64,
farmer_reward_puzzlehash: bytes32,
pool_target: PoolTarget,
get_plot_signature: Callable[[bytes32, G1Element], G2Element],
get_pool_signature: Callable[[PoolTarget, G1Element | None], G2Element | None],
seed: bytes,
compute_fees: Callable[[Sequence[Coin], Sequence[Coin]], uint64],
) -> tuple[Foliage, FoliageTransactionBlock | None, TransactionsInfo | None]:
"""
Creates a foliage for a given reward chain block. This may or may not be a tx block. In the case of a tx block,
the return values are not None. This is called at the signage point, so some of this information may be
tweaked at the infusion point.
Args:
constants: consensus constants being used for this chain
reward_block_unfinished: the reward block to look at, potentially at the signage point
new_block_gen: transactions to add to the foliage block, if created, including aggregate signature
prev_block: the previous block at the signage point
blocks: dict from header hash to blocks, of all ancestor blocks
total_iters_sp: total iters at the signage point
timestamp: timestamp to put into the foliage block
farmer_reward_puzzlehash: where to pay out farming reward
pool_target: where to pay out pool reward
get_plot_signature: retrieve the signature corresponding to the plot public key
get_pool_signature: retrieve the signature corresponding to the pool public key
seed: seed to randomize block
"""
if prev_block is not None:
res = get_prev_transaction_block(prev_block, blocks, total_iters_sp)
is_transaction_block: bool = res[0]
prev_transaction_block: BlockRecord | None = res[1]
else:
# Genesis is a transaction block
prev_transaction_block = None
is_transaction_block = True
rng = random.Random()
rng.seed(seed)
# Use the extension data to create different blocks based on header hash
extension_data: bytes32 = bytes32(rng.randint(0, 100000000).to_bytes(32, "big"))
if prev_block is None:
height: uint32 = uint32(0)
else:
height = uint32(prev_block.height + 1)
# Create filter
byte_array_tx: list[bytearray] = []
tx_additions: list[Coin] = []
tx_removals: list[bytes32] = []
pool_target_signature: G2Element | None = get_pool_signature(
pool_target, reward_block_unfinished.proof_of_space.pool_public_key
)
foliage_data = FoliageBlockData(
reward_block_unfinished.get_hash(),
pool_target,
pool_target_signature,
farmer_reward_puzzlehash,
extension_data,
)
foliage_block_data_signature: G2Element = get_plot_signature(
foliage_data.get_hash(),
reward_block_unfinished.proof_of_space.plot_public_key,
)
prev_block_hash: bytes32 = constants.GENESIS_CHALLENGE
if height != 0:
assert prev_block is not None
prev_block_hash = prev_block.header_hash
generator_block_heights_list: list[uint32] = []
foliage_transaction_block_hash: bytes32 | None
if is_transaction_block:
cost: uint64
spend_bundle_fees: uint64
# Calculate the cost of transactions
if new_block_gen is not None:
cost = new_block_gen.cost
spend_bundle_fees = compute_fees(new_block_gen.additions, new_block_gen.removals)
else:
spend_bundle_fees = uint64(0)
cost = uint64(0)
reward_claims_incorporated = []
if height > 0:
assert prev_transaction_block is not None
assert prev_block is not None
curr: BlockRecord = prev_block
while not curr.is_transaction_block:
curr = blocks.block_record(curr.prev_hash)
assert curr.fees is not None
pool_coin = create_pool_coin(
curr.height, curr.pool_puzzle_hash, calculate_pool_reward(curr.height), constants.GENESIS_CHALLENGE
)
farmer_coin = create_farmer_coin(
curr.height,
curr.farmer_puzzle_hash,
uint64(calculate_base_farmer_reward(curr.height) + curr.fees),
constants.GENESIS_CHALLENGE,
)
assert curr.header_hash == prev_transaction_block.header_hash
reward_claims_incorporated += [pool_coin, farmer_coin]
if curr.height > 0:
curr = blocks.block_record(curr.prev_hash)
# Prev block is not genesis
while not curr.is_transaction_block:
pool_coin = create_pool_coin(
curr.height,
curr.pool_puzzle_hash,
calculate_pool_reward(curr.height),
constants.GENESIS_CHALLENGE,
)
farmer_coin = create_farmer_coin(
curr.height,
curr.farmer_puzzle_hash,
calculate_base_farmer_reward(curr.height),
constants.GENESIS_CHALLENGE,
)
reward_claims_incorporated += [pool_coin, farmer_coin]
curr = blocks.block_record(curr.prev_hash)
for coin in reward_claims_incorporated:
tx_additions.append(coin)
byte_array_tx.append(bytearray(coin.puzzle_hash))
if new_block_gen is not None:
generator_block_heights_list = new_block_gen.block_refs
for coin in new_block_gen.additions:
tx_additions.append(coin)
byte_array_tx.append(bytearray(coin.puzzle_hash))
for coin in new_block_gen.removals:
cname = coin.name()
tx_removals.append(cname)
byte_array_tx.append(bytearray(cname))
bip158: PyBIP158 = PyBIP158(byte_array_tx)
encoded = bytes(bip158.GetEncoded())
additions_merkle_items: list[bytes32] = []
# Create addition Merkle set
puzzlehash_coin_map: dict[bytes32, list[bytes32]] = {}
for coin in tx_additions:
if coin.puzzle_hash in puzzlehash_coin_map:
puzzlehash_coin_map[coin.puzzle_hash].append(coin.name())
else:
puzzlehash_coin_map[coin.puzzle_hash] = [coin.name()]
# Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
for puzzle, coin_ids in puzzlehash_coin_map.items():
additions_merkle_items.append(puzzle)
additions_merkle_items.append(hash_coin_ids(coin_ids))
additions_root = bytes32(compute_merkle_set_root(additions_merkle_items))
removals_root = bytes32(compute_merkle_set_root(tx_removals))
generator_hash = bytes32.zeros
if new_block_gen is not None:
generator_hash = std_hash(new_block_gen.program)
generator_refs_hash = bytes32([1] * 32)
if generator_block_heights_list not in (None, []):
generator_ref_list_bytes = b"".join([i.stream_to_bytes() for i in generator_block_heights_list])
generator_refs_hash = std_hash(generator_ref_list_bytes)
filter_hash: bytes32 = std_hash(encoded)
transactions_info: TransactionsInfo | None = TransactionsInfo(
generator_hash,
generator_refs_hash,
new_block_gen.signature if new_block_gen else G2Element(),
spend_bundle_fees,
cost,
reward_claims_incorporated,
)
if prev_transaction_block is None:
prev_transaction_block_hash: bytes32 = constants.GENESIS_CHALLENGE
else:
prev_transaction_block_hash = prev_transaction_block.header_hash
assert transactions_info is not None
foliage_transaction_block: FoliageTransactionBlock | None = FoliageTransactionBlock(
prev_transaction_block_hash,
timestamp,
filter_hash,
additions_root,
removals_root,
transactions_info.get_hash(),
)
assert foliage_transaction_block is not None
foliage_transaction_block_hash = foliage_transaction_block.get_hash()
foliage_transaction_block_signature: G2Element | None = get_plot_signature(
foliage_transaction_block_hash, reward_block_unfinished.proof_of_space.plot_public_key
)
assert foliage_transaction_block_signature is not None
else:
foliage_transaction_block_hash = None
foliage_transaction_block_signature = None
foliage_transaction_block = None
transactions_info = None
assert (foliage_transaction_block_hash is None) == (foliage_transaction_block_signature is None)
foliage = Foliage(
prev_block_hash,
reward_block_unfinished.get_hash(),
foliage_data,
foliage_block_data_signature,
foliage_transaction_block_hash,
foliage_transaction_block_signature,
)
return foliage, foliage_transaction_block, transactions_info
def create_unfinished_block(
constants: ConsensusConstants,
sub_slot_start_total_iters: uint128,
sub_slot_iters: uint64,
signage_point_index: uint8,
sp_iters: uint64,
ip_iters: uint64,
proof_of_space: ProofOfSpace,
slot_cc_challenge: bytes32,
farmer_reward_puzzle_hash: bytes32,
pool_target: PoolTarget,
get_plot_signature: Callable[[bytes32, G1Element], G2Element],
get_pool_signature: Callable[[PoolTarget, G1Element | None], G2Element | None],
signage_point: SignagePoint,
timestamp: uint64,
blocks: BlockRecordsProtocol,
seed: bytes = b"",
new_block_gen: NewBlockGenerator | None = None,
prev_block: BlockRecord | None = None,
finished_sub_slots_input: list[EndOfSubSlotBundle] | None = None,
compute_fees: Callable[[Sequence[Coin], Sequence[Coin]], uint64] = compute_block_fee,
) -> UnfinishedBlock:
"""
Creates a new unfinished block using all the information available at the signage point. This will have to be
modified using information from the infusion point.
Args:
constants: consensus constants being used for this chain
sub_slot_start_total_iters: the starting sub-slot iters at the signage point sub-slot
sub_slot_iters: sub-slot-iters at the infusion point epoch
signage_point_index: signage point index of the block to create
sp_iters: sp_iters of the block to create
ip_iters: ip_iters of the block to create
proof_of_space: proof of space of the block to create
slot_cc_challenge: challenge hash at the sp sub-slot
farmer_reward_puzzle_hash: where to pay out farmer rewards
pool_target: where to pay out pool rewards
get_plot_signature: function that returns signature corresponding to plot public key
get_pool_signature: function that returns signature corresponding to pool public key
signage_point: signage point information (VDFs)
timestamp: timestamp to add to the foliage block, if created
seed: seed to randomize chain
new_block_gen: transactions to add to the foliage block, if created, including aggregate signature
prev_block: previous block (already in chain) from the signage point
blocks: dictionary from header hash to SBR of all included SBR
finished_sub_slots_input: finished_sub_slots at the signage point
Returns:
"""
if finished_sub_slots_input is None:
finished_sub_slots: list[EndOfSubSlotBundle] = []
else:
finished_sub_slots = finished_sub_slots_input.copy()
overflow: bool = sp_iters > ip_iters
total_iters_sp: uint128 = uint128(sub_slot_start_total_iters + sp_iters)
is_genesis: bool = prev_block is None
new_sub_slot: bool = len(finished_sub_slots) > 0
cc_sp_hash: bytes32 = slot_cc_challenge
# Only enters this if statement if we are in testing mode (making VDF proofs here)
if signage_point.cc_vdf is not None:
assert signage_point.rc_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()
elif is_genesis:
rc_sp_hash = constants.GENESIS_CHALLENGE
else:
assert prev_block is not None
assert blocks is not None
curr = prev_block
while not curr.first_in_sub_slot:
curr = blocks.block_record(curr.prev_hash)
assert curr.finished_reward_slot_hashes is not None
rc_sp_hash = curr.finished_reward_slot_hashes[-1]
signage_point = SignagePoint(None, None, None, None)
cc_sp_signature: G2Element | None = get_plot_signature(cc_sp_hash, proof_of_space.plot_public_key)
rc_sp_signature: G2Element | None = get_plot_signature(rc_sp_hash, proof_of_space.plot_public_key)
assert cc_sp_signature is not None
assert rc_sp_signature is not None
assert chia_rs.AugSchemeMPL.verify(proof_of_space.plot_public_key, cc_sp_hash, cc_sp_signature)
total_iters = uint128(sub_slot_start_total_iters + ip_iters + (sub_slot_iters if overflow else 0))
rc_block = RewardChainBlockUnfinished(
total_iters,
signage_point_index,
slot_cc_challenge,
proof_of_space,
signage_point.cc_vdf,
cc_sp_signature,
signage_point.rc_vdf,
rc_sp_signature,
)
(foliage, foliage_transaction_block, transactions_info) = create_foliage(
constants,
rc_block,
new_block_gen,
prev_block,
blocks,
total_iters_sp,
timestamp,
farmer_reward_puzzle_hash,
pool_target,
get_plot_signature,
get_pool_signature,
seed,
compute_fees,
)
return UnfinishedBlock(
finished_sub_slots,
rc_block,
signage_point.cc_proof,
signage_point.rc_proof,
foliage,
foliage_transaction_block,
transactions_info,
new_block_gen.program if new_block_gen else None,
new_block_gen.block_refs if new_block_gen else [],
)
def unfinished_block_to_full_block(
unfinished_block: UnfinishedBlock,
cc_ip_vdf: VDFInfo,
cc_ip_proof: VDFProof,
rc_ip_vdf: VDFInfo,
rc_ip_proof: VDFProof,
icc_ip_vdf: VDFInfo | None,
icc_ip_proof: VDFProof | None,
finished_sub_slots: list[EndOfSubSlotBundle],
prev_block: BlockRecord | None,
blocks: BlockRecordsProtocol,
total_iters_sp: uint128,
difficulty: uint64,
) -> FullBlock:
"""
Converts an unfinished block to a finished block. Includes all the infusion point VDFs as well as tweaking
other properties (height, weight, sub-slots, etc)
Args:
unfinished_block: the unfinished block to finish
cc_ip_vdf: the challenge chain vdf info at the infusion point
cc_ip_proof: the challenge chain proof
rc_ip_vdf: the reward chain vdf info at the infusion point
rc_ip_proof: the reward chain proof
icc_ip_vdf: the infused challenge chain vdf info at the infusion point
icc_ip_proof: the infused challenge chain proof
finished_sub_slots: finished sub slots from the prev block to the infusion point
prev_block: prev block from the infusion point
blocks: dictionary from header hash to SBR of all included SBR
total_iters_sp: total iters at the signage point
difficulty: difficulty at the infusion point
"""
# Replace things that need to be replaced, since foliage blocks did not necessarily have the latest information
if prev_block is None:
is_transaction_block = True
new_weight = uint128(difficulty)
new_height = uint32(0)
new_foliage_transaction_block = unfinished_block.foliage_transaction_block
new_tx_info = unfinished_block.transactions_info
new_generator = unfinished_block.transactions_generator
new_generator_ref_list = unfinished_block.transactions_generator_ref_list
else:
is_transaction_block, _ = get_prev_transaction_block(prev_block, blocks, total_iters_sp)
new_weight = uint128(prev_block.weight + difficulty)
new_height = uint32(prev_block.height + 1)
if is_transaction_block:
new_foliage_transaction_block = unfinished_block.foliage_transaction_block
new_tx_info = unfinished_block.transactions_info
new_generator = unfinished_block.transactions_generator
new_generator_ref_list = unfinished_block.transactions_generator_ref_list
else:
new_foliage_transaction_block = None
new_tx_info = None
new_generator = None
new_generator_ref_list = []
reward_chain_block = RewardChainBlock(
new_weight,
new_height,
unfinished_block.reward_chain_block.total_iters,
unfinished_block.reward_chain_block.signage_point_index,
unfinished_block.reward_chain_block.pos_ss_cc_challenge_hash,
unfinished_block.reward_chain_block.proof_of_space,
unfinished_block.reward_chain_block.challenge_chain_sp_vdf,
unfinished_block.reward_chain_block.challenge_chain_sp_signature,
cc_ip_vdf,
unfinished_block.reward_chain_block.reward_chain_sp_vdf,
unfinished_block.reward_chain_block.reward_chain_sp_signature,
rc_ip_vdf,
icc_ip_vdf,
None,
is_transaction_block,
)
if prev_block is None:
new_foliage = unfinished_block.foliage.replace(reward_block_hash=reward_chain_block.get_hash())
else:
if is_transaction_block:
new_fbh = unfinished_block.foliage.foliage_transaction_block_hash
new_fbs = unfinished_block.foliage.foliage_transaction_block_signature
else:
new_fbh = None
new_fbs = None
assert (new_fbh is None) == (new_fbs is None)
new_foliage = unfinished_block.foliage.replace(
reward_block_hash=reward_chain_block.get_hash(),
prev_block_hash=prev_block.header_hash,
foliage_transaction_block_hash=new_fbh,
foliage_transaction_block_signature=new_fbs,
)
ret = FullBlock(
finished_sub_slots,
reward_chain_block,
unfinished_block.challenge_chain_sp_proof,
cc_ip_proof,
unfinished_block.reward_chain_sp_proof,
rc_ip_proof,
icc_ip_proof,
new_foliage,
new_foliage_transaction_block,
new_tx_info,
new_generator,
new_generator_ref_list,
)
return ret