Skip to content

Commit

Permalink
the hard fork has activated. From now on we can simplify some of the …
Browse files Browse the repository at this point in the history
…updated consensus rules to apply unconditionally to block height
  • Loading branch information
arvidn committed Jun 18, 2024
1 parent a36c0b8 commit 227ee61
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 141 deletions.
36 changes: 8 additions & 28 deletions chia/_tests/blockchain/test_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import time
from contextlib import asynccontextmanager
from dataclasses import replace
from typing import AsyncIterator, Dict, List, Optional, Tuple
from typing import AsyncIterator, Dict, List, Optional

import pytest
from chia_rs import AugSchemeMPL, G2Element, MerkleSet
Expand Down Expand Up @@ -2072,41 +2072,17 @@ async def test_timelock_conditions(
],
)
@pytest.mark.parametrize(
"with_garbage,expected",
[
(True, (AddBlockResult.INVALID_BLOCK, Err.INVALID_CONDITION, None)),
(False, (AddBlockResult.NEW_PEAK, None, 2)),
],
"with_garbage",
[True, False],
)
async def test_aggsig_garbage(
self,
empty_blockchain: Blockchain,
opcode: ConditionOpcode,
with_garbage: bool,
expected: Tuple[AddBlockResult, Optional[Err], Optional[uint32]],
bt: BlockTools,
consensus_mode: ConsensusMode,
) -> None:
# in the 2.0 hard fork, we relax the strict 2-parameters rule of
# AGG_SIG_* conditions, in consensus mode. In mempool mode we always
# apply strict rules.
if consensus_mode >= ConsensusMode.HARD_FORK_2_0 and with_garbage:
expected = (AddBlockResult.NEW_PEAK, None, uint32(2))

# before the 2.0 hard fork, these conditions do not exist
# but WalletTool still lets us create them, and aggregate them into the
# block signature. When the pre-hard fork node sees them, the conditions
# are ignored, but the aggregate signature is corrupt.
if consensus_mode < ConsensusMode.HARD_FORK_2_0 and opcode in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]:
expected = (AddBlockResult.INVALID_BLOCK, Err.BAD_AGGREGATE_SIGNATURE, None)

b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
Expand Down Expand Up @@ -2153,7 +2129,11 @@ async def test_aggsig_garbage(
# Ignore errors from pre-validation, we are testing block_body_validation
repl_preval_results = replace(pre_validation_results[0], error=None, required_iters=uint64(1))
res, error, state_change = await b.add_block(blocks[-1], repl_preval_results, None)
assert (res, error, state_change.fork_height if state_change else None) == expected
assert (res, error, state_change.fork_height if state_change else None) == (
AddBlockResult.NEW_PEAK,
None,
uint32(2),
)

@pytest.mark.anyio
@pytest.mark.parametrize("with_garbage", [True, False])
Expand Down
18 changes: 2 additions & 16 deletions chia/_tests/core/full_node/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,6 @@ async def test_unknown_conditions_with_cost(
conditions = Program.to(assemble(f"(({opcode} 1337))"))
additions, removals, new_block = await check_conditions(bt, conditions)

if consensus_mode < ConsensusMode.HARD_FORK_2_0:
# before the hard fork, all unknown conditions have 0 cost
expected_cost = 0

# once the hard fork activates, blocks no longer pay the cost of the ROM
# generator (which includes hashing all puzzles).
if consensus_mode >= ConsensusMode.HARD_FORK_2_0:
Expand All @@ -172,8 +168,6 @@ async def test_softfork_condition(
additions, removals, new_block = await check_conditions(bt, conditions)

if consensus_mode < ConsensusMode.HARD_FORK_2_0:
# the SOFTFORK condition is not recognized before the hard fork
expected_cost = 0
block_base_cost = 737056
else:
# once the hard fork activates, blocks no longer pay the cost of the ROM
Expand Down Expand Up @@ -533,16 +527,7 @@ async def test_agg_sig_illegal_suffix(
assert c.AGG_SIG_PARENT_PUZZLE_ADDITIONAL_DATA == additional_data[ConditionOpcode.AGG_SIG_PARENT_PUZZLE]

blocks = await initial_blocks(bt)
if consensus_mode < ConsensusMode.HARD_FORK_2_0 and opcode in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]:
expected_error = Err.BAD_AGGREGATE_SIGNATURE
elif opcode == ConditionOpcode.AGG_SIG_UNSAFE:
if opcode == ConditionOpcode.AGG_SIG_UNSAFE:
expected_error = Err.INVALID_CONDITION
else:
expected_error = None
Expand All @@ -551,6 +536,7 @@ async def test_agg_sig_illegal_suffix(
pubkey = sk.get_g1()
coin = blocks[-2].get_included_reward_coins()[0]
for msg in [
c.AGG_SIG_ME_ADDITIONAL_DATA,
c.AGG_SIG_PARENT_ADDITIONAL_DATA,
c.AGG_SIG_PUZZLE_ADDITIONAL_DATA,
c.AGG_SIG_AMOUNT_ADDITIONAL_DATA,
Expand Down
66 changes: 10 additions & 56 deletions chia/_tests/core/mempool/test_mempool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2004,15 +2004,15 @@ def test_invalid_condition_args_terminator(self, softfork_height):
assert npc_result.conds.spends[0].seconds_relative == 50

@pytest.mark.parametrize(
"mempool,operand,expected",
"mempool,operand",
[
(True, -1, Err.GENERATOR_RUNTIME_ERROR.value),
(False, -1, Err.GENERATOR_RUNTIME_ERROR.value),
(True, 1, None),
(False, 1, None),
(True, -1),
(False, -1),
(True, 1),
(False, 1),
],
)
def test_div(self, mempool, operand, expected, softfork_height):
def test_div(self, mempool, operand, softfork_height):
# op_div is disallowed on negative numbers in the mempool, and after the
# softfork
npc_result = generator_condition_tester(
Expand All @@ -2022,11 +2022,8 @@ def test_div(self, mempool, operand, expected, softfork_height):
height=softfork_height,
)

# with the 2.0 hard fork, division with negative numbers is allowed
if operand < 0 and softfork_height >= test_constants.HARD_FORK_HEIGHT:
expected = None

assert npc_result.error == expected
# after the 2.0 hard fork, division with negative numbers is allowed
assert npc_result.error is None

def test_invalid_condition_list_terminator(self, softfork_height):
# note how the list of conditions isn't correctly terminated with a
Expand Down Expand Up @@ -2166,17 +2163,7 @@ def test_agg_sig_cost(self, condition, softfork_height):
else:
generator_base_cost = 20512

if softfork_height < test_constants.HARD_FORK_HEIGHT and condition in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]:
expected_cost = 0
else:
expected_cost = ConditionCost.AGG_SIG.value
expected_cost = ConditionCost.AGG_SIG.value

# this max cost is exactly enough for the AGG_SIG condition
npc_result = generator_condition_tester(
Expand Down Expand Up @@ -2219,41 +2206,12 @@ def test_agg_sig_cost(self, condition, softfork_height):
def test_agg_sig_extra_arg(self, condition, extra_arg, mempool, softfork_height):
pubkey = "0x" + bytes(G1Element.generator()).hex()

new_condition = condition in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]

hard_fork_activated = softfork_height >= test_constants.HARD_FORK_HEIGHT

expected_error = None

# in mempool mode, we don't allow extra arguments
if mempool and extra_arg != "":
expected_error = Err.INVALID_CONDITION.value

# the original AGG_SIG_* conditions had a quirk (fixed in the hard fork)
# where they always required exactly two arguments, regardless of
# mempool or not. After the hard fork, they behave like all other
# conditions
if not new_condition and not hard_fork_activated and extra_arg != "":
expected_error = Err.INVALID_CONDITION.value

# except before the hard fork has activated, new conditions are just
# unknown
if new_condition and not hard_fork_activated:
else:
expected_error = None

# before the hard fork activates, the new conditions are unknown and
# fail in mempool mode, regardless of whether they have extra arguments
# or not
if new_condition and not hard_fork_activated and mempool:
expected_error = Err.INVALID_CONDITION.value

# this max cost is exactly enough for the AGG_SIG condition
npc_result = generator_condition_tester(
f'({condition[0]} {pubkey} "foobar"{extra_arg}) ',
Expand Down Expand Up @@ -2361,10 +2319,6 @@ def test_softfork_condition(
# in mempool all unknown conditions are always a failure
if mempool:
expect_error = Err.INVALID_CONDITION.value
# the SOFTFORK condition is only activated with the hard fork, so
# before then there are no errors
elif softfork_height < test_constants.HARD_FORK_HEIGHT:
expect_error = None

assert npc_result.error == expect_error

Expand Down
29 changes: 3 additions & 26 deletions chia/full_node/mempool_check_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,38 +40,14 @@


def get_flags_for_height_and_constants(height: int, constants: ConsensusConstants) -> int:
flags = 0
flags = ENABLE_SOFTFORK_CONDITION | ENABLE_BLS_OPS_OUTSIDE_GUARD | ENABLE_FIXED_DIV | AGG_SIG_ARGS | ALLOW_BACKREFS

if height >= constants.SOFT_FORK4_HEIGHT:
flags = flags | ENABLE_MESSAGE_CONDITIONS

if height >= constants.SOFT_FORK5_HEIGHT:
flags = flags | DISALLOW_INFINITY_G1

if height >= constants.HARD_FORK_HEIGHT:
# the hard-fork initiated with 2.1. To activate June 2024
# * costs are ascribed to some unknown condition codes, to allow for
# soft-forking in new conditions with cost
# * a new condition, SOFTFORK, is added which takes a first parameter to
# specify its cost. This allows soft-forks similar to the softfork
# operator
# * BLS operators introduced in the soft-fork (behind the softfork
# guard) are made available outside of the guard.
# * division with negative numbers are allowed, and round toward
# negative infinity
# * AGG_SIG_* conditions are allowed to have unknown additional
# arguments
# * Allow the block generator to be serialized with the improved clvm
# serialization format (with back-references)
flags = (
flags
| ENABLE_SOFTFORK_CONDITION
| ENABLE_BLS_OPS_OUTSIDE_GUARD
| ENABLE_FIXED_DIV
| AGG_SIG_ARGS
| ALLOW_BACKREFS
)

return flags


Expand All @@ -83,14 +59,15 @@ def get_name_puzzle_conditions(
height: uint32,
constants: ConsensusConstants,
) -> NPCResult:
run_block = run_block_generator
flags = get_flags_for_height_and_constants(height, constants)

if mempool_mode:
flags = flags | MEMPOOL_MODE

if height >= constants.HARD_FORK_HEIGHT:
run_block = run_block_generator2
else:
run_block = run_block_generator

try:
block_args = [bytes(gen) for gen in generator.generator_refs]
Expand Down
30 changes: 15 additions & 15 deletions chia/simulator/block_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1911,22 +1911,13 @@ def compute_cost_table() -> List[int]:
CONDITION_COSTS = compute_cost_table()


def conditions_cost(conds: Program, hard_fork: bool) -> uint64:
def conditions_cost(conds: Program) -> uint64:
condition_cost = 0
for cond in conds.as_iter():
condition = cond.first().as_atom()
if condition in [ConditionOpcode.AGG_SIG_UNSAFE, ConditionOpcode.AGG_SIG_ME]:
condition_cost += ConditionCost.AGG_SIG.value
elif condition == ConditionOpcode.CREATE_COIN:
condition_cost += ConditionCost.CREATE_COIN.value
# after the 2.0 hard fork, two byte conditions (with no leading 0)
# have costs. Account for that.
elif hard_fork and len(condition) == 2 and condition[0] != 0:
condition_cost += CONDITION_COSTS[condition[1]]
elif hard_fork and condition == ConditionOpcode.SOFTFORK.value:
arg = cond.rest().first().as_int()
condition_cost += arg * 10000
elif hard_fork and condition in [
if condition in [
ConditionOpcode.AGG_SIG_UNSAFE,
ConditionOpcode.AGG_SIG_ME,
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
Expand All @@ -1935,6 +1926,15 @@ def conditions_cost(conds: Program, hard_fork: bool) -> uint64:
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]:
condition_cost += ConditionCost.AGG_SIG.value
elif condition == ConditionOpcode.CREATE_COIN:
condition_cost += ConditionCost.CREATE_COIN.value
# after the 2.0 hard fork, two byte conditions (with no leading 0)
# have costs. Account for that.
elif len(condition) == 2 and condition[0] != 0:
condition_cost += CONDITION_COSTS[condition[1]]
elif condition == ConditionOpcode.SOFTFORK.value:
arg = cond.rest().first().as_int()
condition_cost += arg * 10000
return uint64(condition_cost)


Expand Down Expand Up @@ -1975,7 +1975,7 @@ def compute_cost_test(generator: BlockGenerator, constants: ConsensusConstants,

cost, result = puzzle._run(INFINITE_COST, MEMPOOL_MODE, solution)
clvm_cost += cost
condition_cost += conditions_cost(result, height >= constants.HARD_FORK_HEIGHT)
condition_cost += conditions_cost(result)

else:
block_program_args = SerializedProgram.to([[bytes(g) for g in generator.generator_refs]])
Expand All @@ -1985,7 +1985,7 @@ def compute_cost_test(generator: BlockGenerator, constants: ConsensusConstants,
# each condition item is:
# (parent-coin-id puzzle-hash amount conditions)
conditions = res.at("rrrf")
condition_cost += conditions_cost(conditions, height >= constants.HARD_FORK_HEIGHT)
condition_cost += conditions_cost(conditions)

size_cost = len(bytes(generator.program)) * constants.COST_PER_BYTE

Expand Down

0 comments on commit 227ee61

Please sign in to comment.