Skip to content

Commit

Permalink
Simplify return values for validate_block_body.
Browse files Browse the repository at this point in the history
  • Loading branch information
AmineKhaldi committed Oct 29, 2024
1 parent 6dee78a commit e68bca4
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 49 deletions.
5 changes: 2 additions & 3 deletions chia/_tests/wallet/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from chia._tests.util.setup_nodes import setup_simulators_and_wallets_service
from chia._tests.wallet.wallet_block_tools import WalletBlockTools
from chia.consensus.constants import ConsensusConstants
from chia.consensus.cost_calculator import NPCResult
from chia.full_node.full_node import FullNode
from chia.rpc.full_node_rpc_client import FullNodeRpcClient
from chia.rpc.wallet_rpc_client import WalletRpcClient
Expand Down Expand Up @@ -56,8 +55,8 @@ async def ignore_block_validation(
if "standard_block_tools" in request.keywords:
return None

async def validate_block_body(*args: Any, **kwargs: Any) -> tuple[Literal[None], NPCResult]:
return None, args[7]
async def validate_block_body(*args: Any, **kwargs: Any) -> Literal[None]:
return None

def create_wrapper(original_create: Any) -> Any:
async def new_create(*args: Any, **kwargs: Any) -> Any:
Expand Down
84 changes: 42 additions & 42 deletions chia/consensus/block_body_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,12 @@ async def validate_block_body(
bls_cache: Optional[BLSCache],
*,
validate_signature: bool = True,
) -> tuple[Optional[Err], Optional[SpendBundleConditions]]:
) -> Optional[Err]:
"""
This assumes the header block has been completely validated.
Validates the transactions and body of the block.
Returns None for the first value if everything validates correctly, or an
Err if something does not validate.
For the second value, returns a SpendBundleConditions only if validation
succeeded, and there are transactions. In other cases it returns None.
Returns None if everything validates correctly, or an Err if something does
not validate.
conds is the result of running the generator with the previous generators
refs. It must be set for transaction blocks and must be None for
non-transaction blocks.
Expand All @@ -220,7 +218,7 @@ async def validate_block_body(
or block.transactions_info is not None
or block.transactions_generator is not None
):
return Err.NOT_BLOCK_BUT_HAS_DATA, None
return Err.NOT_BLOCK_BUT_HAS_DATA

prev_tb: Optional[BlockRecord] = records.block_record(block.prev_header_hash)
assert prev_tb is not None
Expand All @@ -229,28 +227,30 @@ async def validate_block_body(
assert prev_tb is not None
assert prev_tb.timestamp is not None
if len(block.transactions_generator_ref_list) > 0:
return Err.NOT_BLOCK_BUT_HAS_DATA, None
return Err.NOT_BLOCK_BUT_HAS_DATA

assert fork_info.peak_height == height - 1

return None, None # This means the block is valid
assert conds is None
# This means the block is valid
return None

# All checks below this point correspond to transaction blocks
# 2. For blocks, foliage block, transactions info must not be empty
if block.foliage_transaction_block is None or block.transactions_info is None:
return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None
return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA
assert block.foliage_transaction_block is not None

# keeps track of the reward coins that need to be incorporated
expected_reward_coins: set[Coin] = set()

# 3. The transaction info hash in the Foliage block must match the transaction info
if block.foliage_transaction_block.transactions_info_hash != std_hash(block.transactions_info):
return Err.INVALID_TRANSACTIONS_INFO_HASH, None
return Err.INVALID_TRANSACTIONS_INFO_HASH

# 4. The foliage block hash in the foliage block must match the foliage block
if block.foliage.foliage_transaction_block_hash != std_hash(block.foliage_transaction_block):
return Err.INVALID_FOLIAGE_BLOCK_HASH, None
return Err.INVALID_FOLIAGE_BLOCK_HASH

# 5. The reward claims must be valid for the previous blocks, and current block fees
# If height == 0, expected_reward_coins will be left empty
Expand Down Expand Up @@ -303,10 +303,10 @@ async def validate_block_body(
assert curr_b is not None

if set(block.transactions_info.reward_claims_incorporated) != expected_reward_coins:
return Err.INVALID_REWARD_COINS, None
return Err.INVALID_REWARD_COINS

if len(block.transactions_info.reward_claims_incorporated) != len(expected_reward_coins):
return Err.INVALID_REWARD_COINS, None
return Err.INVALID_REWARD_COINS

removals: list[bytes32] = []

Expand All @@ -326,31 +326,31 @@ async def validate_block_body(
# the generator for this block (or zeroes if no generator)
if block.transactions_generator is not None:
if std_hash(bytes(block.transactions_generator)) != block.transactions_info.generator_root:
return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None
return Err.INVALID_TRANSACTIONS_GENERATOR_HASH
else:
if block.transactions_info.generator_root != bytes([0] * 32):
return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None
return Err.INVALID_TRANSACTIONS_GENERATOR_HASH

# 8a. The generator_ref_list must be the hash of the serialized bytes of
# the generator ref list for this block (or 'one' bytes [0x01] if no generator)
# 8b. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries
# 8c. The generator ref list must not point to a height >= this block's height
if block.transactions_generator_ref_list in (None, []):
if block.transactions_info.generator_refs_root != bytes([1] * 32):
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
else:
# If we have a generator reference list, we must have a generator
if block.transactions_generator is None:
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT

# The generator_refs_root must be the hash of the concatenation of the list[uint32]
generator_refs_hash = std_hash(b"".join([i.stream_to_bytes() for i in block.transactions_generator_ref_list]))
if block.transactions_info.generator_refs_root != generator_refs_hash:
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
if len(block.transactions_generator_ref_list) > constants.MAX_GENERATOR_REF_LIST_SIZE:
return Err.TOO_MANY_GENERATOR_REFS, None
return Err.TOO_MANY_GENERATOR_REFS
if any([index >= height for index in block.transactions_generator_ref_list]):
return Err.FUTURE_GENERATOR_REFS, None
return Err.FUTURE_GENERATOR_REFS

if block.transactions_generator is not None:
# Get List of names removed, puzzles hashes for removed coins and conditions created
Expand All @@ -363,7 +363,7 @@ async def validate_block_body(
f"percent full: {round(100 * (cost / constants.MAX_BLOCK_COST_CLVM), 2)}%"
)
if cost > constants.MAX_BLOCK_COST_CLVM:
return Err.BLOCK_COST_EXCEEDS_MAX, None
return Err.BLOCK_COST_EXCEEDS_MAX

# 8. The CLVM program must not return any errors
assert conds is not None
Expand All @@ -379,7 +379,7 @@ async def validate_block_body(

# 9. Check that the correct cost is in the transactions info
if block.transactions_info.cost != cost:
return Err.INVALID_BLOCK_COST, None
return Err.INVALID_BLOCK_COST

additions_dic: dict[bytes32, Coin] = {}
# 10. Check additions for max coin amount
Expand All @@ -388,10 +388,10 @@ async def validate_block_body(
for coin, coin_name in additions + coinbase_additions:
additions_dic[coin_name] = coin
if coin.amount < 0:
return Err.COIN_AMOUNT_NEGATIVE, None
return Err.COIN_AMOUNT_NEGATIVE

if coin.amount > constants.MAX_COIN_AMOUNT:
return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None
return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM

# 11. Validate addition and removal roots
root_error = validate_block_merkle_roots(
Expand All @@ -400,8 +400,8 @@ async def validate_block_body(
additions + coinbase_additions,
removals,
)
if root_error:
return root_error, None
if root_error is not None:
return root_error

# 12. The additions and removals must result in the correct filter
byte_array_tx: list[bytearray] = []
Expand All @@ -416,19 +416,19 @@ async def validate_block_body(
filter_hash = std_hash(encoded_filter)

if filter_hash != block.foliage_transaction_block.filter_hash:
return Err.INVALID_TRANSACTIONS_FILTER_HASH, None
return Err.INVALID_TRANSACTIONS_FILTER_HASH

# 13. Check for duplicate outputs in additions
addition_counter = collections.Counter(coin_name for _, coin_name in additions + coinbase_additions)
for count in addition_counter.values():
if count > 1:
return Err.DUPLICATE_OUTPUT, None
return Err.DUPLICATE_OUTPUT

# 14. Check for duplicate spends inside block
removal_counter = collections.Counter(removals)
for count in removal_counter.values():
if count > 1:
return Err.DOUBLE_SPEND, None
return Err.DOUBLE_SPEND

# 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block)
# The fork point is the last block in common between the peak chain and the chain of `block`
Expand Down Expand Up @@ -458,7 +458,7 @@ async def validate_block_body(
if rem in fork_info.removals_since_fork:
# This coin was spent in the fork
log.error(f"Err.DOUBLE_SPEND_IN_FORK {fork_info.removals_since_fork[rem]}")
return Err.DOUBLE_SPEND_IN_FORK, None
return Err.DOUBLE_SPEND_IN_FORK
removals_from_db.append(rem)

unspent_records = await get_coin_records(removals_from_db)
Expand All @@ -472,7 +472,7 @@ async def validate_block_body(
# (We ignore all coins confirmed after fork)
if unspent.spent == 1 and unspent.spent_block_index <= fork_info.fork_height:
# Check for coins spent in an ancestor block
return Err.DOUBLE_SPEND, None
return Err.DOUBLE_SPEND
removal_coin_records[unspent.name] = unspent
else:
look_in_fork.append(unspent.name)
Expand All @@ -491,7 +491,7 @@ async def validate_block_body(
if rem not in fork_info.additions_since_fork:
# Check for spending a coin that does not exist in this fork
log.error(f"Err.UNKNOWN_UNSPENT: COIN ID: {rem} fork_info: {fork_info}")
return Err.UNKNOWN_UNSPENT, None
return Err.UNKNOWN_UNSPENT
addition: ForkAdd = fork_info.additions_since_fork[rem]
new_coin_record: CoinRecord = CoinRecord(
addition.coin,
Expand All @@ -512,7 +512,7 @@ async def validate_block_body(

# 16. Check that the total coin amount for added is <= removed
if removed < added:
return Err.MINTING_COIN, None
return Err.MINTING_COIN

fees = removed - added
assert fees >= 0
Expand All @@ -523,20 +523,20 @@ async def validate_block_body(

# 17. Check that the assert fee sum <= fees, and that each reserved fee is non-negative
if fees < assert_fee_sum:
return Err.RESERVE_FEE_CONDITION_FAILED, None
return Err.RESERVE_FEE_CONDITION_FAILED

# 18. Check that the fee amount + farmer reward < maximum coin amount
if fees + calculate_base_farmer_reward(height) > constants.MAX_COIN_AMOUNT:
return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None
return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM

# 19. Check that the computed fees are equal to the fees in the block header
if block.transactions_info.fees != fees:
return Err.INVALID_BLOCK_FEE_AMOUNT, None
return Err.INVALID_BLOCK_FEE_AMOUNT

# 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes
for unspent in removal_coin_records.values():
if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]:
return Err.WRONG_PUZZLE_HASH, None
return Err.WRONG_PUZZLE_HASH

# 21. Verify conditions
# verify absolute/relative height/time conditions
Expand All @@ -548,7 +548,7 @@ async def validate_block_body(
prev_transaction_block_timestamp,
)
if error is not None:
return error, None
return error

# create hash_key list for aggsig check
pairs_pks: list[G1Element] = []
Expand All @@ -559,7 +559,7 @@ async def validate_block_body(
# 22. Verify aggregated signature
# TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster
if not block.transactions_info.aggregated_signature:
return Err.BAD_AGGREGATE_SIGNATURE, None
return Err.BAD_AGGREGATE_SIGNATURE

# The pairing cache is not useful while syncing as each pairing is seen
# only once, so the extra effort of populating it is not justified.
Expand All @@ -569,9 +569,9 @@ async def validate_block_body(
if validate_signature:
if bls_cache is None:
if not AugSchemeMPL.aggregate_verify(pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature):
return Err.BAD_AGGREGATE_SIGNATURE, None
return Err.BAD_AGGREGATE_SIGNATURE
else:
if not bls_cache.aggregate_verify(pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature):
return Err.BAD_AGGREGATE_SIGNATURE, None
return Err.BAD_AGGREGATE_SIGNATURE

return None, conds
return None
9 changes: 5 additions & 4 deletions chia/consensus/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ async def add_block(
assert fork_info.peak_height == block.height - 1
assert block.height == 0 or fork_info.peak_hash == block.prev_header_hash

error_code, _ = await validate_block_body(
error_code = await validate_block_body(
self.constants,
self,
self.coin_store.get_coin_records,
Expand Down Expand Up @@ -712,13 +712,14 @@ async def validate_unfinished_block(

fork_info = ForkInfo(prev_height, prev_height, block.prev_header_hash)

error_code, cost_result = await validate_block_body(
conds = None if npc_result is None else npc_result.conds
error_code = await validate_block_body(
self.constants,
self,
self.coin_store.get_coin_records,
block,
uint32(prev_height + 1),
None if npc_result is None else npc_result.conds,
conds,
fork_info,
None,
validate_signature=False, # Signature was already validated before calling this method, no need to validate
Expand All @@ -727,7 +728,7 @@ async def validate_unfinished_block(
if error_code is not None:
return PreValidationResult(uint16(error_code.value), None, None, False, uint32(0))

return PreValidationResult(None, required_iters, cost_result, False, uint32(0))
return PreValidationResult(None, required_iters, conds, False, uint32(0))

def contains_block(self, header_hash: bytes32) -> bool:
"""
Expand Down

0 comments on commit e68bca4

Please sign in to comment.