infrastructure for upcoming soft-fork (#14302)

soft-fork infrastructure
This commit is contained in:
Arvid Norberg 2023-01-12 10:08:59 +01:00 committed by GitHub
parent 2f5bbd6875
commit 0f9e4ee41a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 134 additions and 74 deletions

View File

@ -318,6 +318,7 @@ async def validate_block_body(
min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost),
cost_per_byte=constants.COST_PER_BYTE,
mempool_mode=False,
height=curr.height,
)
removals_in_curr, additions_in_curr = tx_removals_and_additions(curr_npc_result.conds)
else:

View File

@ -443,6 +443,7 @@ class Blockchain(BlockchainInterface):
self.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=self.constants.COST_PER_BYTE,
mempool_mode=False,
height=block.height,
)
tx_removals, tx_additions = tx_removals_and_additions(npc_result.conds)
return tx_removals, tx_additions, npc_result
@ -646,13 +647,14 @@ class Blockchain(BlockchainInterface):
validate_signatures=validate_signatures,
)
async def run_generator(self, unfinished_block: bytes, generator: BlockGenerator) -> NPCResult:
async def run_generator(self, unfinished_block: bytes, generator: BlockGenerator, height: uint32) -> NPCResult:
task = asyncio.get_running_loop().run_in_executor(
self.pool,
_run_generator,
self.constants,
unfinished_block,
bytes(generator),
height,
)
npc_result_bytes = await task
if npc_result_bytes is None:

View File

@ -61,6 +61,7 @@ class ConsensusConstants:
MAX_GENERATOR_SIZE: uint32
MAX_GENERATOR_REF_LIST_SIZE: uint32
POOL_SUB_SLOT_ITERS: uint64
SOFT_FORK_HEIGHT: uint32
def replace(self, **changes: object) -> "ConsensusConstants":
return dataclasses.replace(self, **changes)

View File

@ -55,6 +55,7 @@ default_kwargs = {
"MAX_GENERATOR_SIZE": 1000000,
"MAX_GENERATOR_REF_LIST_SIZE": 512, # Number of references allowed in the block generator ref list
"POOL_SUB_SLOT_ITERS": 37600000000, # iters limit * NUM_SPS
"SOFT_FORK_HEIGHT": 3630000,
}

View File

@ -93,6 +93,7 @@ def batch_pre_validate_blocks(
min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost),
cost_per_byte=constants.COST_PER_BYTE,
mempool_mode=False,
height=block.height,
)
removals, tx_additions = tx_removals_and_additions(npc_result.conds)
if npc_result is not None and npc_result.error is not None:
@ -368,6 +369,7 @@ def _run_generator(
constants: ConsensusConstants,
unfinished_block_bytes: bytes,
block_generator_bytes: bytes,
height: uint32,
) -> Optional[bytes]:
"""
Runs the CLVM generator from bytes inputs. This is meant to be called under a ProcessPoolExecutor, in order to
@ -383,6 +385,7 @@ def _run_generator(
min(constants.MAX_BLOCK_COST_CLVM, unfinished_block.transactions_info.cost),
cost_per_byte=constants.COST_PER_BYTE,
mempool_mode=False,
height=height,
)
return bytes(npc_result)
except ValidationError as e:

View File

@ -1883,7 +1883,8 @@ class FullNode:
if block_bytes is None:
block_bytes = bytes(block)
npc_result = await self.blockchain.run_generator(block_bytes, block_generator)
height = uint32(0) if prev_b is None else uint32(prev_b.height + 1)
npc_result = await self.blockchain.run_generator(block_bytes, block_generator, height)
pre_validation_time = time.time() - pre_validation_start
# blockchain.run_generator throws on errors, so npc_result is

View File

@ -1114,6 +1114,7 @@ class FullNodeAPI:
self.full_node.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=self.full_node.constants.COST_PER_BYTE,
mempool_mode=False,
height=request.height,
),
)

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import logging
from typing import Dict, Optional, Tuple
from chia_rs import MEMPOOL_MODE, NO_NEG_DIV
from chia_rs import LIMIT_STACK, MEMPOOL_MODE, NO_NEG_DIV
from chia_rs import get_puzzle_and_solution_for_coin as get_puzzle_and_solution_for_coin_rust
from chia.consensus.cost_calculator import NPCResult
@ -30,7 +30,7 @@ log = logging.getLogger(__name__)
def get_name_puzzle_conditions(
generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool
generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool, height: Optional[uint32] = None
) -> NPCResult:
block_program, block_program_args = setup_generator_args(generator)
size_cost = len(bytes(generator.program)) * cost_per_byte
@ -38,11 +38,14 @@ def get_name_puzzle_conditions(
if max_cost < 0:
return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), None, uint64(0))
# mempool mode also has these rules apply
assert (MEMPOOL_MODE & NO_NEG_DIV) != 0
# in mempool mode, the height doesn't matter, because it's always strict.
# But otherwise, height must be specified to know which rules to apply
assert mempool_mode or height is not None
if mempool_mode:
flags = MEMPOOL_MODE
elif height is not None and height >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT:
flags = NO_NEG_DIV | LIMIT_STACK
else:
# conditions must use integers in canonical encoding (i.e. no redundant
# leading zeros)

View File

@ -243,7 +243,7 @@ class TestBlockHeaderValidation:
assert empty_blockchain.get_peak().height == len(blocks) - 1
@pytest.mark.asyncio
async def test_unfinished_blocks(self, empty_blockchain, bt):
async def test_unfinished_blocks(self, empty_blockchain, softfork_height, bt):
blockchain = empty_blockchain
blocks = bt.get_consecutive_blocks(3)
for block in blocks[:-1]:
@ -264,7 +264,7 @@ class TestBlockHeaderValidation:
if unf.transactions_generator is not None:
block_generator: BlockGenerator = await blockchain.get_block_generator(unf)
block_bytes = bytes(unf)
npc_result = await blockchain.run_generator(block_bytes, block_generator)
npc_result = await blockchain.run_generator(block_bytes, block_generator, height=softfork_height)
validate_res = await blockchain.validate_unfinished_block(unf, npc_result, False)
err = validate_res.error
@ -288,7 +288,7 @@ class TestBlockHeaderValidation:
if unf.transactions_generator is not None:
block_generator: BlockGenerator = await blockchain.get_block_generator(unf)
block_bytes = bytes(unf)
npc_result = await blockchain.run_generator(block_bytes, block_generator)
npc_result = await blockchain.run_generator(block_bytes, block_generator, height=softfork_height)
validate_res = await blockchain.validate_unfinished_block(unf, npc_result, False)
assert validate_res.error is None
@ -333,7 +333,7 @@ class TestBlockHeaderValidation:
assert blockchain.get_peak().height == num_blocks - 1
@pytest.mark.asyncio
async def test_unf_block_overflow(self, empty_blockchain, bt):
async def test_unf_block_overflow(self, empty_blockchain, softfork_height, bt):
blockchain = empty_blockchain
blocks = []
@ -367,7 +367,7 @@ class TestBlockHeaderValidation:
if block.transactions_generator is not None:
block_generator: BlockGenerator = await blockchain.get_block_generator(unf)
block_bytes = bytes(unf)
npc_result = await blockchain.run_generator(block_bytes, block_generator)
npc_result = await blockchain.run_generator(block_bytes, block_generator, height=softfork_height)
validate_res = await blockchain.validate_unfinished_block(
unf, npc_result, skip_overflow_ss_validation=True
)
@ -2262,7 +2262,7 @@ class TestBodyValidation:
)
@pytest.mark.asyncio
async def test_cost_exceeds_max(self, empty_blockchain, bt):
async def test_cost_exceeds_max(self, empty_blockchain, softfork_height, bt):
# 7
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
@ -2296,6 +2296,7 @@ class TestBodyValidation:
b.constants.MAX_BLOCK_COST_CLVM * 1000,
cost_per_byte=b.constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
err = (await b.receive_block(blocks[-1], PreValidationResult(None, uint64(1), npc_result, True)))[1]
assert err in [Err.BLOCK_COST_EXCEEDS_MAX]
@ -2312,7 +2313,7 @@ class TestBodyValidation:
pass
@pytest.mark.asyncio
async def test_invalid_cost_in_block(self, empty_blockchain, bt):
async def test_invalid_cost_in_block(self, empty_blockchain, softfork_height, bt):
# 9
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
@ -2356,6 +2357,7 @@ class TestBodyValidation:
min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost),
cost_per_byte=b.constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
result, err, _ = await b.receive_block(block_2, PreValidationResult(None, uint64(1), npc_result, False))
assert err == Err.INVALID_BLOCK_COST
@ -2380,6 +2382,7 @@ class TestBodyValidation:
min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost),
cost_per_byte=b.constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
result, err, _ = await b.receive_block(block_2, PreValidationResult(None, uint64(1), npc_result, False))
assert err == Err.INVALID_BLOCK_COST
@ -2404,6 +2407,7 @@ class TestBodyValidation:
min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost),
cost_per_byte=b.constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
result, err, _ = await b.receive_block(block_2, PreValidationResult(None, uint64(1), npc_result, False))

View File

@ -267,7 +267,7 @@ class TestBlockchainTransactions:
await full_node_api_1.full_node.respond_block(full_node_protocol.RespondBlock(block))
@pytest.mark.asyncio
async def test_validate_blockchain_spend_reorg_coin(self, two_nodes):
async def test_validate_blockchain_spend_reorg_coin(self, two_nodes, softfork_height):
num_blocks = 10
wallet_a = WALLET_A
coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0]
@ -308,6 +308,7 @@ class TestBlockchainTransactions:
new_blocks[-1],
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
height=softfork_height,
)[1]:
if coin.puzzle_hash == receiver_1_puzzlehash:
coin_2 = coin
@ -331,6 +332,7 @@ class TestBlockchainTransactions:
new_blocks[-1],
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
height=softfork_height,
)[1]:
if coin.puzzle_hash == receiver_2_puzzlehash:
coin_3 = coin

View File

@ -132,6 +132,11 @@ def db_version(request):
return request.param
@pytest.fixture(scope="function", params=[1000000, 3630000])
def softfork_height(request):
return request.param
saved_blocks_version = "rc5"

View File

@ -51,7 +51,7 @@ def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]:
class TestCoinStoreWithBlocks:
@pytest.mark.asyncio
async def test_basic_coin_store(self, db_version, bt):
async def test_basic_coin_store(self, db_version, softfork_height, bt):
wallet_a = WALLET_A
reward_ph = wallet_a.get_new_puzzlehash()
@ -100,6 +100,7 @@ class TestCoinStoreWithBlocks:
bt.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=bt.constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
tx_removals, tx_additions = tx_removals_and_additions(npc_result.conds)
else:

View File

@ -1981,6 +1981,7 @@ def generator_condition_tester(
mempool_mode: bool = False,
quote: bool = True,
max_cost: int = MAX_BLOCK_COST_CLVM,
height: uint32,
) -> NPCResult:
prg = f"(q ((0x0101010101010101010101010101010101010101010101010101010101010101 {'(q ' if quote else ''} {conditions} {')' if quote else ''} 123 (() (q . ())))))" # noqa
print(f"program: {prg}")
@ -1988,18 +1989,18 @@ def generator_condition_tester(
generator = BlockGenerator(program, [], [])
print(f"len: {len(bytes(program))}")
npc_result: NPCResult = get_name_puzzle_conditions(
generator, max_cost, cost_per_byte=COST_PER_BYTE, mempool_mode=mempool_mode
generator, max_cost, cost_per_byte=COST_PER_BYTE, mempool_mode=mempool_mode, height=height
)
return npc_result
class TestGeneratorConditions:
def test_invalid_condition_args_terminator(self):
def test_invalid_condition_args_terminator(self, softfork_height):
# note how the condition argument list isn't correctly terminated with a
# NIL atom. This is allowed, and all arguments beyond the ones we look
# at are ignored, including the termination of the list
npc_result = generator_condition_tester("(80 50 . 1)")
npc_result = generator_condition_tester("(80 50 . 1)", height=softfork_height)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
assert npc_result.conds.spends[0].seconds_relative == 50
@ -2009,24 +2010,29 @@ class TestGeneratorConditions:
[
(True, -1, Err.GENERATOR_RUNTIME_ERROR.value),
(False, -1, Err.GENERATOR_RUNTIME_ERROR.value),
(False, -1, Err.GENERATOR_RUNTIME_ERROR.value),
(True, 1, None),
(False, 1, None),
(False, 1, None),
],
)
def test_div(self, mempool, operand, expected):
def test_div(self, mempool, operand, expected, softfork_height):
# op_div is disallowed on negative numbers in the mempool, and after the
# softfork
npc_result = generator_condition_tester(
f"(c (c (q . 80) (c (/ (q . 50) (q . {operand})) ())) ())", quote=False, mempool_mode=mempool
f"(c (c (q . 80) (c (/ (q . 50) (q . {operand})) ())) ())",
quote=False,
mempool_mode=mempool,
height=softfork_height,
)
assert npc_result.error == expected
def test_invalid_condition_list_terminator(self):
def test_invalid_condition_list_terminator(self, softfork_height):
# note how the list of conditions isn't correctly terminated with a
# NIL atom. This is a failure
npc_result = generator_condition_tester("(80 50) . 3")
npc_result = generator_condition_tester("(80 50) . 3", height=softfork_height)
assert npc_result.error in [Err.INVALID_CONDITION.value, Err.GENERATOR_RUNTIME_ERROR.value]
@pytest.mark.parametrize(
@ -2038,10 +2044,12 @@ class TestGeneratorConditions:
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
],
)
def test_duplicate_height_time_conditions(self, opcode):
def test_duplicate_height_time_conditions(self, opcode, softfork_height):
# even though the generator outputs multiple conditions, we only
# need to return the highest one (i.e. most strict)
npc_result = generator_condition_tester(" ".join([f"({opcode.value[0]} {i})" for i in range(50, 101)]))
npc_result = generator_condition_tester(
" ".join([f"({opcode.value[0]} {i})" for i in range(50, 101)]), height=softfork_height
)
print(npc_result)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
@ -2063,11 +2071,11 @@ class TestGeneratorConditions:
ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT,
],
)
def test_just_announcement(self, opcode):
def test_just_announcement(self, opcode, softfork_height):
message = "a" * 1024
# announcements are validated on the Rust side and never returned
# back. They are either satisified or cause an immediate failure
npc_result = generator_condition_tester(f'({opcode.value[0]} "{message}") ' * 50)
npc_result = generator_condition_tester(f'({opcode.value[0]} "{message}") ' * 50, height=softfork_height)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
# create-announcements and assert-announcements are dropped once
@ -2080,36 +2088,36 @@ class TestGeneratorConditions:
ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT,
],
)
def test_assert_announcement_fail(self, opcode):
def test_assert_announcement_fail(self, opcode, softfork_height):
message = "a" * 1024
# announcements are validated on the Rust side and never returned
# back. They ar either satisified or cause an immediate failure
# in this test we just assert announcements, we never make them, so
# these should fail
npc_result = generator_condition_tester(f'({opcode.value[0]} "{message}") ')
npc_result = generator_condition_tester(f'({opcode.value[0]} "{message}") ', height=softfork_height)
print(npc_result)
assert npc_result.error == Err.ASSERT_ANNOUNCE_CONSUMED_FAILED.value
def test_multiple_reserve_fee(self):
def test_multiple_reserve_fee(self, softfork_height):
# RESERVE_FEE
cond = 52
# even though the generator outputs 3 conditions, we only need to return one copy
# with all the fees accumulated
npc_result = generator_condition_tester(f"({cond} 100) " * 3)
npc_result = generator_condition_tester(f"({cond} 100) " * 3, height=softfork_height)
assert npc_result.error is None
assert npc_result.conds.reserve_fee == 300
assert len(npc_result.conds.spends) == 1
def test_duplicate_outputs(self):
def test_duplicate_outputs(self, softfork_height):
# CREATE_COIN
# creating multiple coins with the same properties (same parent, same
# target puzzle hash and same amount) is not allowed. That's a consensus
# failure.
puzzle_hash = "abababababababababababababababab"
npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 10) ' * 2)
npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 10) ' * 2, height=softfork_height)
assert npc_result.error == Err.DUPLICATE_OUTPUT.value
def test_create_coin_cost(self):
def test_create_coin_cost(self, softfork_height):
# CREATE_COIN
puzzle_hash = "abababababababababababababababab"
@ -2117,6 +2125,7 @@ class TestGeneratorConditions:
npc_result = generator_condition_tester(
f'(51 "{puzzle_hash}" 10) ',
max_cost=20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value,
height=softfork_height,
)
assert npc_result.error is None
assert npc_result.cost == 20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value
@ -2127,10 +2136,11 @@ class TestGeneratorConditions:
npc_result = generator_condition_tester(
f'(51 "{puzzle_hash}" 10) ',
max_cost=20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value - 1,
height=softfork_height,
)
assert npc_result.error in [Err.BLOCK_COST_EXCEEDS_MAX.value, Err.INVALID_BLOCK_COST.value]
def test_agg_sig_cost(self):
def test_agg_sig_cost(self, softfork_height):
# AGG_SIG_ME
pubkey = "abababababababababababababababababababababababab"
@ -2138,6 +2148,7 @@ class TestGeneratorConditions:
npc_result = generator_condition_tester(
f'(49 "{pubkey}" "foobar") ',
max_cost=20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value,
height=softfork_height,
)
assert npc_result.error is None
assert npc_result.cost == 20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value
@ -2147,10 +2158,11 @@ class TestGeneratorConditions:
npc_result = generator_condition_tester(
f'(49 "{pubkey}" "foobar") ',
max_cost=20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value - 1,
height=softfork_height,
)
assert npc_result.error in [Err.BLOCK_COST_EXCEEDS_MAX.value, Err.INVALID_BLOCK_COST.value]
def test_create_coin_different_parent(self):
def test_create_coin_different_parent(self, softfork_height):
# if the coins we create have different parents, they are never
# considered duplicate, even when they have the same puzzle hash and
@ -2163,49 +2175,60 @@ class TestGeneratorConditions:
)
generator = BlockGenerator(program, [], [])
npc_result: NPCResult = get_name_puzzle_conditions(
generator, MAX_BLOCK_COST_CLVM, cost_per_byte=COST_PER_BYTE, mempool_mode=False
generator, MAX_BLOCK_COST_CLVM, cost_per_byte=COST_PER_BYTE, mempool_mode=False, height=softfork_height
)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 2
for s in npc_result.conds.spends:
assert s.create_coin == [(puzzle_hash.encode("ascii"), 10, None)]
def test_create_coin_different_puzzhash(self):
def test_create_coin_different_puzzhash(self, softfork_height):
# CREATE_COIN
# coins with different puzzle hashes are not considered duplicate
puzzle_hash_1 = "abababababababababababababababab"
puzzle_hash_2 = "cbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcb"
npc_result = generator_condition_tester(f'(51 "{puzzle_hash_1}" 5) (51 "{puzzle_hash_2}" 5)')
npc_result = generator_condition_tester(
f'(51 "{puzzle_hash_1}" 5) (51 "{puzzle_hash_2}" 5)', height=softfork_height
)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
assert (puzzle_hash_1.encode("ascii"), 5, None) in npc_result.conds.spends[0].create_coin
assert (puzzle_hash_2.encode("ascii"), 5, None) in npc_result.conds.spends[0].create_coin
def test_create_coin_different_amounts(self):
def test_create_coin_different_amounts(self, softfork_height):
# CREATE_COIN
# coins with different amounts are not considered duplicate
puzzle_hash = "abababababababababababababababab"
npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 5) (51 "{puzzle_hash}" 4)')
npc_result = generator_condition_tester(
f'(51 "{puzzle_hash}" 5) (51 "{puzzle_hash}" 4)', height=softfork_height
)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
coins = npc_result.conds.spends[0].create_coin
assert (puzzle_hash.encode("ascii"), 5, None) in coins
assert (puzzle_hash.encode("ascii"), 4, None) in coins
def test_create_coin_with_hint(self):
def test_create_coin_with_hint(self, softfork_height):
# CREATE_COIN
puzzle_hash_1 = "abababababababababababababababab"
hint = "12341234123412341234213421341234"
npc_result = generator_condition_tester(f'(51 "{puzzle_hash_1}" 5 ("{hint}"))')
npc_result = generator_condition_tester(f'(51 "{puzzle_hash_1}" 5 ("{hint}"))', height=softfork_height)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
coins = npc_result.conds.spends[0].create_coin
assert coins == [(puzzle_hash_1.encode("ascii"), 5, hint.encode("ascii"))]
@pytest.mark.parametrize("mempool", [True, False, False])
def test_unknown_condition(self, mempool: bool):
@pytest.mark.parametrize(
"mempool,height",
[
(True, None),
(False, 2300000),
(False, 3630000),
],
)
def test_unknown_condition(self, mempool: bool, height: uint32):
for c in ['(2 100 "foo" "bar")', "(100)", "(4 1) (2 2) (3 3)", '("foobar")']:
npc_result = generator_condition_tester(c, mempool_mode=mempool)
npc_result = generator_condition_tester(c, mempool_mode=mempool, height=height)
print(npc_result)
if mempool:
assert npc_result.error == Err.INVALID_CONDITION.value
@ -2340,11 +2363,11 @@ class TestMaliciousGenerators:
],
)
@pytest.mark.benchmark
def test_duplicate_large_integer_ladder(self, request, opcode):
def test_duplicate_large_integer_ladder(self, request, opcode, softfork_height):
condition = SINGLE_ARG_INT_LADDER_COND.format(opcode=opcode.value[0], num=28, filler="0x00")
with assert_runtime(seconds=0.7, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error == error_for_condition(opcode)
@ -2358,11 +2381,11 @@ class TestMaliciousGenerators:
],
)
@pytest.mark.benchmark
def test_duplicate_large_integer(self, request, opcode):
def test_duplicate_large_integer(self, request, opcode, softfork_height):
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
with assert_runtime(seconds=1.1, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error == error_for_condition(opcode)
@ -2376,11 +2399,11 @@ class TestMaliciousGenerators:
],
)
@pytest.mark.benchmark
def test_duplicate_large_integer_substr(self, request, opcode):
def test_duplicate_large_integer_substr(self, request, opcode, softfork_height):
condition = SINGLE_ARG_INT_SUBSTR_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
with assert_runtime(seconds=1.1, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error == error_for_condition(opcode)
@ -2394,13 +2417,13 @@ class TestMaliciousGenerators:
],
)
@pytest.mark.benchmark
def test_duplicate_large_integer_substr_tail(self, request, opcode):
def test_duplicate_large_integer_substr_tail(self, request, opcode, softfork_height):
condition = SINGLE_ARG_INT_SUBSTR_TAIL_COND.format(
opcode=opcode.value[0], num=280, val="0xffffffff", filler="0x00"
)
with assert_runtime(seconds=0.3, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error == error_for_condition(opcode)
@ -2414,32 +2437,32 @@ class TestMaliciousGenerators:
],
)
@pytest.mark.benchmark
def test_duplicate_large_integer_negative(self, request, opcode):
def test_duplicate_large_integer_negative(self, request, opcode, softfork_height):
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0xff")
with assert_runtime(seconds=1, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
@pytest.mark.benchmark
def test_duplicate_reserve_fee(self, request):
def test_duplicate_reserve_fee(self, request, softfork_height):
opcode = ConditionOpcode.RESERVE_FEE
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
with assert_runtime(seconds=1, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error == error_for_condition(opcode)
@pytest.mark.benchmark
def test_duplicate_reserve_fee_negative(self, request: pytest.FixtureRequest):
def test_duplicate_reserve_fee_negative(self, request: pytest.FixtureRequest, softfork_height):
opcode = ConditionOpcode.RESERVE_FEE
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=200000, val=100, filler="0xff")
with assert_runtime(seconds=0.8, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
# RESERVE_FEE conditions fail unconditionally if they have a negative
# amount
@ -2450,11 +2473,11 @@ class TestMaliciousGenerators:
"opcode", [ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT]
)
@pytest.mark.benchmark
def test_duplicate_coin_announces(self, request, opcode):
def test_duplicate_coin_announces(self, request, opcode, softfork_height):
condition = CREATE_ANNOUNCE_COND.format(opcode=opcode.value[0], num=5950000)
with assert_runtime(seconds=9, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1
@ -2462,7 +2485,7 @@ class TestMaliciousGenerators:
# TODO: optimize clvm to make this run in < 1 second
@pytest.mark.benchmark
def test_create_coin_duplicates(self, request: pytest.FixtureRequest):
def test_create_coin_duplicates(self, request: pytest.FixtureRequest, softfork_height):
# CREATE_COIN
# this program will emit 6000 identical CREATE_COIN conditions. However,
# we'll just end up looking at two of them, and fail at the first
@ -2470,13 +2493,13 @@ class TestMaliciousGenerators:
condition = CREATE_COIN.format(num=600000)
with assert_runtime(seconds=0.8, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error == Err.DUPLICATE_OUTPUT.value
assert npc_result.conds is None
@pytest.mark.benchmark
def test_many_create_coin(self, request):
def test_many_create_coin(self, request, softfork_height):
# CREATE_COIN
# this program will emit many CREATE_COIN conditions, all with different
# amounts.
@ -2484,7 +2507,7 @@ class TestMaliciousGenerators:
condition = CREATE_UNIQUE_COINS.format(num=6094)
with assert_runtime(seconds=0.3, label=request.node.name):
npc_result = generator_condition_tester(condition, quote=False)
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
assert npc_result.error is None
assert len(npc_result.conds.spends) == 1

View File

@ -54,7 +54,7 @@ def large_block_generator(size):
class TestCostCalculation:
@pytest.mark.asyncio
async def test_basics(self, bt):
async def test_basics(self, softfork_height, bt):
wallet_tool = bt.get_pool_wallet_tool()
ph = wallet_tool.get_new_puzzlehash()
num_blocks = 3
@ -80,6 +80,7 @@ class TestCostCalculation:
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
assert npc_result.error is None
@ -113,7 +114,7 @@ class TestCostCalculation:
)
@pytest.mark.asyncio
async def test_mempool_mode(self, bt):
async def test_mempool_mode(self, softfork_height, bt):
wallet_tool = bt.get_pool_wallet_tool()
ph = wallet_tool.get_new_puzzlehash()
@ -152,6 +153,7 @@ class TestCostCalculation:
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
mempool_mode=True,
height=softfork_height,
)
assert npc_result.error is not None
npc_result = get_name_puzzle_conditions(
@ -159,6 +161,7 @@ class TestCostCalculation:
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
assert npc_result.error is None
@ -171,7 +174,7 @@ class TestCostCalculation:
assert error is None
@pytest.mark.asyncio
async def test_clvm_mempool_mode(self):
async def test_clvm_mempool_mode(self, softfork_height):
block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program))
disassembly = binutils.disassemble(block)
# this is a valid generator program except the first clvm
@ -192,12 +195,13 @@ class TestCostCalculation:
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
assert npc_result.error is None
@pytest.mark.asyncio
@pytest.mark.benchmark
async def test_tx_generator_speed(self, request):
async def test_tx_generator_speed(self, request, softfork_height):
LARGE_BLOCK_COIN_CONSUMED_COUNT = 687
generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT)
program = SerializedProgram.from_bytes(generator_bytes)
@ -209,13 +213,14 @@ class TestCostCalculation:
test_constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=test_constants.COST_PER_BYTE,
mempool_mode=False,
height=softfork_height,
)
assert npc_result.error is None
assert len(npc_result.conds.spends) == LARGE_BLOCK_COIN_CONSUMED_COUNT
@pytest.mark.asyncio
async def test_clvm_max_cost(self):
async def test_clvm_max_cost(self, softfork_height):
block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program))
disassembly = binutils.disassemble(block)
@ -235,6 +240,7 @@ class TestCostCalculation:
10000000,
cost_per_byte=0,
mempool_mode=False,
height=softfork_height,
)
assert npc_result.error is not None
@ -242,7 +248,9 @@ class TestCostCalculation:
# raise the max cost to make sure this passes
# ensure we pass if the program does not exceeds the cost
npc_result = get_name_puzzle_conditions(generator, 23000000, cost_per_byte=0, mempool_mode=False)
npc_result = get_name_puzzle_conditions(
generator, 23000000, cost_per_byte=0, mempool_mode=False, height=softfork_height
)
assert npc_result.error is None
assert npc_result.cost > 10000000

View File

@ -94,14 +94,16 @@ class TestROM:
assert cost == EXPECTED_ABBREVIATED_COST
assert r.as_bin().hex() == EXPECTED_OUTPUT
def test_get_name_puzzle_conditions(self):
def test_get_name_puzzle_conditions(self, softfork_height):
# this tests that extra block or coin data doesn't confuse `get_name_puzzle_conditions`
gen = block_generator()
cost, r = run_generator_unsafe(gen, max_cost=MAX_COST)
print(r)
npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, mempool_mode=False)
npc_result = get_name_puzzle_conditions(
gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, mempool_mode=False, height=softfork_height
)
assert npc_result.error is None
assert npc_result.cost == EXPECTED_COST + ConditionCost.CREATE_COIN.value + (
len(bytes(gen.program)) * COST_PER_BYTE

View File

@ -8,10 +8,11 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.full_block import FullBlock
from chia.types.generator_types import BlockGenerator
from chia.util.generator_tools import tx_removals_and_additions
from chia.util.ints import uint32
def run_and_get_removals_and_additions(
block: FullBlock, max_cost: int, *, cost_per_byte: int, mempool_mode=False
block: FullBlock, max_cost: int, *, cost_per_byte: int, height: uint32, mempool_mode=False
) -> Tuple[List[bytes32], List[Coin]]:
removals: List[bytes32] = []
additions: List[Coin] = []
@ -26,6 +27,7 @@ def run_and_get_removals_and_additions(
max_cost,
cost_per_byte=cost_per_byte,
mempool_mode=mempool_mode,
height=height,
)
assert npc_result.error is None
rem, add = tx_removals_and_additions(npc_result.conds)