mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-11-09 17:36:14 +03:00
infrastructure for upcoming soft-fork (#14302)
soft-fork infrastructure
This commit is contained in:
parent
2f5bbd6875
commit
0f9e4ee41a
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user