mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
introduce restrictions on generators at a specific height (#9957)
* introduce restrictions on generators at a specific height. disallow division on negative numbers and disallow redundant leading zeros on integer condition arguments (produced by a generator) * use SOFT_FORK_HEIGHT constant * there is no need to specify height when validating block in block_creation * Update tests/core/full_node/test_mempool.py Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com> Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>
This commit is contained in:
co-authored by
dustinface
parent
8b838731d9
commit
b8ada1ceb7
@@ -320,6 +320,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.npc_list)
|
||||
else:
|
||||
|
||||
@@ -455,6 +455,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.npc_list)
|
||||
return tx_removals, tx_additions, npc_result
|
||||
@@ -634,13 +635,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_json,
|
||||
unfinished_block,
|
||||
bytes(generator),
|
||||
height,
|
||||
)
|
||||
npc_result_bytes = await task
|
||||
if npc_result_bytes is None:
|
||||
|
||||
@@ -59,6 +59,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) -> "ConsensusConstants":
|
||||
return dataclasses.replace(self, **changes)
|
||||
|
||||
@@ -54,6 +54,7 @@ testnet_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": 2000000,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -90,6 +90,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.npc_list)
|
||||
if npc_result is not None and npc_result.error is not None:
|
||||
@@ -370,6 +371,7 @@ def _run_generator(
|
||||
constants_dict: bytes,
|
||||
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
|
||||
@@ -386,6 +388,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:
|
||||
|
||||
@@ -1675,7 +1675,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
|
||||
|
||||
pairs_pks, pairs_msgs = pkm_pairs(npc_result.npc_list, self.constants.AGG_SIG_ME_ADDITIONAL_DATA)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, List, Optional
|
||||
from clvm_rs import STRICT_MODE as MEMPOOL_MODE
|
||||
from clvm_rs import MEMPOOL_MODE, COND_CANON_INTS, NO_NEG_DIV
|
||||
|
||||
from clvm.casts import int_from_bytes, int_to_bytes
|
||||
from chia.consensus.cost_calculator import NPCResult
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.full_node.generator import create_generator_args, setup_generator_args
|
||||
from chia.types.blockchain_format.program import NIL
|
||||
from chia.types.coin_record import CoinRecord
|
||||
@@ -107,8 +108,13 @@ def add_cond(
|
||||
conds[op].append(ConditionWithArgs(op, args))
|
||||
|
||||
|
||||
def unwrap(x: Optional[uint32]) -> uint32:
|
||||
assert x is not None
|
||||
return x
|
||||
|
||||
|
||||
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
|
||||
@@ -116,7 +122,24 @@ def get_name_puzzle_conditions(
|
||||
if max_cost < 0:
|
||||
return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0))
|
||||
|
||||
flags = MEMPOOL_MODE if mempool_mode else 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
|
||||
|
||||
# mempool mode also has these rules apply
|
||||
assert (MEMPOOL_MODE & COND_CANON_INTS) != 0
|
||||
assert (MEMPOOL_MODE & NO_NEG_DIV) != 0
|
||||
|
||||
if mempool_mode:
|
||||
flags = MEMPOOL_MODE
|
||||
elif unwrap(height) >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT:
|
||||
# conditions must use integers in canonical encoding (i.e. no redundant
|
||||
# leading zeros)
|
||||
# the division operator may not be used with negative operands
|
||||
flags = COND_CANON_INTS | NO_NEG_DIV
|
||||
else:
|
||||
flags = 0
|
||||
|
||||
try:
|
||||
err, result = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from clvm.casts import int_from_bytes
|
||||
from clvm.EvalError import EvalError
|
||||
from clvm.operators import OPERATOR_LOOKUP
|
||||
from clvm.serialize import sexp_from_stream, sexp_to_stream
|
||||
from clvm_rs import STRICT_MODE as MEMPOOL_MODE, run_chia_program, serialized_length, run_generator2
|
||||
from clvm_rs import MEMPOOL_MODE, run_chia_program, serialized_length, run_generator2
|
||||
from clvm_tools.curry import curry, uncurry
|
||||
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
|
||||
@@ -17,7 +17,11 @@ def compute_coin_hints(cs: CoinSpend) -> List[bytes]:
|
||||
generator = simple_solution_generator(bundle)
|
||||
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
generator, INFINITE_COST, cost_per_byte=DEFAULT_CONSTANTS.COST_PER_BYTE, mempool_mode=False
|
||||
generator,
|
||||
INFINITE_COST,
|
||||
cost_per_byte=DEFAULT_CONSTANTS.COST_PER_BYTE,
|
||||
mempool_mode=False,
|
||||
height=DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT,
|
||||
)
|
||||
h_list = []
|
||||
for npc in npc_result.npc_list:
|
||||
|
||||
@@ -8,7 +8,7 @@ dependencies = [
|
||||
"chiabip158==1.1", # bip158-style wallet filters
|
||||
"chiapos==1.0.8", # proof of space
|
||||
"clvm==0.9.7",
|
||||
"clvm_rs==0.1.17",
|
||||
"clvm_rs==0.1.19",
|
||||
"clvm_tools==0.4.3",
|
||||
"aiohttp==3.7.4", # HTTP server for full node rpc
|
||||
"aiosqlite==0.17.0", # asyncio wrapper for sqlite, to store blocks
|
||||
|
||||
@@ -251,7 +251,7 @@ class TestBlockHeaderValidation:
|
||||
assert empty_blockchain.get_peak().height == len(blocks) - 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unfinished_blocks(self, empty_blockchain):
|
||||
async def test_unfinished_blocks(self, empty_blockchain, softfork_height):
|
||||
blockchain = empty_blockchain
|
||||
blocks = bt.get_consecutive_blocks(3)
|
||||
for block in blocks[:-1]:
|
||||
@@ -272,7 +272,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
|
||||
@@ -296,7 +296,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
|
||||
|
||||
@@ -342,7 +342,7 @@ class TestBlockHeaderValidation:
|
||||
assert blockchain.get_peak().height == num_blocks - 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unf_block_overflow(self, empty_blockchain):
|
||||
async def test_unf_block_overflow(self, empty_blockchain, softfork_height):
|
||||
blockchain = empty_blockchain
|
||||
|
||||
blocks = []
|
||||
@@ -376,7 +376,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
|
||||
)
|
||||
@@ -2165,7 +2165,7 @@ class TestBodyValidation:
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cost_exceeds_max(self, empty_blockchain):
|
||||
async def test_cost_exceeds_max(self, empty_blockchain, softfork_height):
|
||||
# 7
|
||||
b = empty_blockchain
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
@@ -2199,6 +2199,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]
|
||||
@@ -2215,7 +2216,7 @@ class TestBodyValidation:
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_cost_in_block(self, empty_blockchain):
|
||||
async def test_invalid_cost_in_block(self, empty_blockchain, softfork_height):
|
||||
# 9
|
||||
b = empty_blockchain
|
||||
blocks = bt.get_consecutive_blocks(
|
||||
@@ -2259,6 +2260,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
|
||||
@@ -2283,6 +2285,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
|
||||
@@ -2307,6 +2310,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))
|
||||
|
||||
@@ -281,7 +281,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]
|
||||
@@ -320,7 +320,10 @@ class TestBlockchainTransactions:
|
||||
|
||||
coin_2 = None
|
||||
for coin in run_and_get_removals_and_additions(
|
||||
new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE
|
||||
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
|
||||
@@ -341,7 +344,10 @@ class TestBlockchainTransactions:
|
||||
|
||||
coin_3 = None
|
||||
for coin in run_and_get_removals_and_additions(
|
||||
new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE
|
||||
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
|
||||
|
||||
@@ -35,6 +35,11 @@ def db_version(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", params=[1000000, 2000000])
|
||||
def softfork_height(request):
|
||||
return request.param
|
||||
|
||||
|
||||
block_format_version = "rc4"
|
||||
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]:
|
||||
class TestCoinStoreWithBlocks:
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("cache_size", [0])
|
||||
async def test_basic_coin_store(self, cache_size: uint32, db_version):
|
||||
async def test_basic_coin_store(self, cache_size: uint32, db_version, softfork_height):
|
||||
wallet_a = WALLET_A
|
||||
reward_ph = wallet_a.get_new_puzzlehash()
|
||||
|
||||
@@ -107,6 +107,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.npc_list)
|
||||
else:
|
||||
|
||||
@@ -26,7 +26,7 @@ from chia.types.mempool_item import MempoolItem
|
||||
from chia.util.clvm import int_to_bytes
|
||||
from chia.util.condition_tools import conditions_for_solution, pkm_pairs
|
||||
from chia.util.errors import Err
|
||||
from chia.util.ints import uint64
|
||||
from chia.util.ints import uint64, uint32
|
||||
from chia.util.hash import std_hash
|
||||
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
||||
from chia.util.api_decorators import api_request, peer_required, bytes_required
|
||||
@@ -1762,6 +1762,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}")
|
||||
@@ -1769,18 +1770,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.npc_list) == 1
|
||||
opcode = ConditionOpcode(bytes([80]))
|
||||
@@ -1790,11 +1791,31 @@ class TestGeneratorConditions:
|
||||
c = npc_result.npc_list[0].conditions[0][1][0]
|
||||
assert c == ConditionWithArgs(opcode=ConditionOpcode.ASSERT_SECONDS_RELATIVE, vars=[bytes([50])])
|
||||
|
||||
def test_invalid_condition_list_terminator(self):
|
||||
@pytest.mark.parametrize(
|
||||
"mempool,height,operand,expected",
|
||||
[
|
||||
(True, 0, -1, Err.GENERATOR_RUNTIME_ERROR.value),
|
||||
(False, 1000000, -1, None),
|
||||
(False, 2000000, -1, Err.GENERATOR_RUNTIME_ERROR.value),
|
||||
(True, 0, 1, None),
|
||||
(False, 1000000, 1, None),
|
||||
(False, 2000000, 1, None),
|
||||
],
|
||||
)
|
||||
def test_div(self, mempool, height, operand, expected):
|
||||
|
||||
# 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, height=height
|
||||
)
|
||||
assert npc_result.error == expected
|
||||
|
||||
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(
|
||||
@@ -1806,10 +1827,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.npc_list) == 1
|
||||
@@ -1827,11 +1850,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.npc_list) == 1
|
||||
# create-announcements and assert-announcements are dropped once
|
||||
@@ -1845,23 +1868,23 @@ 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
|
||||
assert npc_result.npc_list == []
|
||||
|
||||
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 len(npc_result.npc_list) == 1
|
||||
opcode = ConditionOpcode(bytes([cond]))
|
||||
@@ -1875,23 +1898,25 @@ class TestGeneratorConditions:
|
||||
assert reserve_fee == 300
|
||||
assert len(npc_result.npc_list[0].conditions[0][1]) == 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
|
||||
assert npc_result.npc_list == []
|
||||
|
||||
def test_create_coin_cost(self):
|
||||
def test_create_coin_cost(self, softfork_height):
|
||||
# CREATE_COIN
|
||||
puzzle_hash = "abababababababababababababababab"
|
||||
|
||||
# this max cost is exactly enough for the create coin condition
|
||||
npc_result = generator_condition_tester(
|
||||
f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value
|
||||
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
|
||||
@@ -1899,17 +1924,21 @@ class TestGeneratorConditions:
|
||||
|
||||
# if we subtract one from max cost, this should fail
|
||||
npc_result = generator_condition_tester(
|
||||
f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value - 1
|
||||
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"
|
||||
|
||||
# this max cost is exactly enough for the AGG_SIG condition
|
||||
npc_result = generator_condition_tester(
|
||||
f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value
|
||||
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
|
||||
@@ -1917,11 +1946,13 @@ class TestGeneratorConditions:
|
||||
|
||||
# if we subtract one from max cost, this should fail
|
||||
npc_result = generator_condition_tester(
|
||||
f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value - 1
|
||||
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
|
||||
@@ -1934,7 +1965,7 @@ 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.npc_list) == 2
|
||||
@@ -1947,12 +1978,14 @@ class TestGeneratorConditions:
|
||||
)
|
||||
]
|
||||
|
||||
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.npc_list) == 1
|
||||
opcode = ConditionOpcode.CREATE_COIN
|
||||
@@ -1965,11 +1998,13 @@ class TestGeneratorConditions:
|
||||
in npc_result.npc_list[0].conditions[0][1]
|
||||
)
|
||||
|
||||
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.npc_list) == 1
|
||||
opcode = ConditionOpcode.CREATE_COIN
|
||||
@@ -1982,11 +2017,11 @@ class TestGeneratorConditions:
|
||||
in npc_result.npc_list[0].conditions[0][1]
|
||||
)
|
||||
|
||||
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.npc_list) == 1
|
||||
opcode = ConditionOpcode.CREATE_COIN
|
||||
@@ -1995,14 +2030,18 @@ class TestGeneratorConditions:
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mempool_mode",
|
||||
[True, False],
|
||||
"mempool,height",
|
||||
[
|
||||
(True, None),
|
||||
(False, 1000000),
|
||||
(False, 2000000),
|
||||
],
|
||||
)
|
||||
def test_unknown_condition(self, mempool_mode):
|
||||
def test_unknown_condition(self, mempool, height):
|
||||
for c in ['(1 100 "foo" "bar")', "(100)", "(1 1) (2 2) (3 3)", '("foobar")']:
|
||||
npc_result = generator_condition_tester(c, mempool_mode=mempool_mode)
|
||||
npc_result = generator_condition_tester(c, mempool_mode=mempool, height=height)
|
||||
print(npc_result)
|
||||
if mempool_mode:
|
||||
if mempool:
|
||||
assert npc_result.error == Err.INVALID_CONDITION.value
|
||||
assert npc_result.npc_list == []
|
||||
else:
|
||||
@@ -2104,6 +2143,22 @@ CREATE_COIN = '(a (q 2 2 (c 2 (c (q 51 "abababababababababababababababab" 1) (c
|
||||
CREATE_UNIQUE_COINS = '(a (q 2 6 (c 2 (c (q 51 "abababababababababababababababab") (c 5 ())))) (c (q (a (i 5 (q 4 9 (a 4 (c 2 (c 13 (c 11 ()))))) (q 4 11 ())) 1) 2 (i 11 (q 4 (a 4 (c 2 (c 5 (c 11 ())))) (a 6 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # noqa
|
||||
|
||||
|
||||
# some of the malicious tests will fail post soft-fork, this function helps test
|
||||
# the specific error to expect
|
||||
def error_for_condition(cond: ConditionOpcode) -> int:
|
||||
if cond == ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE:
|
||||
return Err.ASSERT_HEIGHT_ABSOLUTE_FAILED.value
|
||||
if cond == ConditionOpcode.ASSERT_HEIGHT_RELATIVE:
|
||||
return Err.ASSERT_HEIGHT_RELATIVE_FAILED.value
|
||||
if cond == ConditionOpcode.ASSERT_SECONDS_ABSOLUTE:
|
||||
return Err.ASSERT_SECONDS_ABSOLUTE_FAILED.value
|
||||
if cond == ConditionOpcode.ASSERT_SECONDS_RELATIVE:
|
||||
return Err.ASSERT_SECONDS_RELATIVE_FAILED.value
|
||||
if cond == ConditionOpcode.RESERVE_FEE:
|
||||
return Err.RESERVE_FEE_CONDITION_FAILED.value
|
||||
assert False
|
||||
|
||||
|
||||
class TestMaliciousGenerators:
|
||||
|
||||
# TODO: create a lot of announcements. The messages can be made different by
|
||||
@@ -2120,19 +2175,22 @@ class TestMaliciousGenerators:
|
||||
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
|
||||
],
|
||||
)
|
||||
def test_duplicate_large_integer_ladder(self, opcode):
|
||||
def test_duplicate_large_integer_ladder(self, opcode, softfork_height):
|
||||
condition = SINGLE_ARG_INT_LADDER_COND.format(opcode=opcode.value[0], num=28, filler="0x00")
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode,
|
||||
[ConditionWithArgs(opcode, [int_to_bytes(28)])],
|
||||
)
|
||||
]
|
||||
if softfork_height >= 2000000:
|
||||
assert npc_result.error == error_for_condition(opcode)
|
||||
else:
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode,
|
||||
[ConditionWithArgs(opcode, [int_to_bytes(28)])],
|
||||
)
|
||||
]
|
||||
assert run_time < 1.5
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
@@ -2145,19 +2203,22 @@ class TestMaliciousGenerators:
|
||||
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
|
||||
],
|
||||
)
|
||||
def test_duplicate_large_integer(self, opcode):
|
||||
def test_duplicate_large_integer(self, opcode, softfork_height):
|
||||
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode,
|
||||
[ConditionWithArgs(opcode, [bytes([100])])],
|
||||
)
|
||||
]
|
||||
if softfork_height >= 2000000:
|
||||
assert npc_result.error == error_for_condition(opcode)
|
||||
else:
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode,
|
||||
[ConditionWithArgs(opcode, [bytes([100])])],
|
||||
)
|
||||
]
|
||||
assert run_time < 2.5
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
@@ -2170,19 +2231,22 @@ class TestMaliciousGenerators:
|
||||
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
|
||||
],
|
||||
)
|
||||
def test_duplicate_large_integer_substr(self, opcode):
|
||||
def test_duplicate_large_integer_substr(self, opcode, softfork_height):
|
||||
condition = SINGLE_ARG_INT_SUBSTR_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode,
|
||||
[ConditionWithArgs(opcode, [bytes([100])])],
|
||||
)
|
||||
]
|
||||
if softfork_height >= 2000000:
|
||||
assert npc_result.error == error_for_condition(opcode)
|
||||
else:
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode,
|
||||
[ConditionWithArgs(opcode, [bytes([100])])],
|
||||
)
|
||||
]
|
||||
assert run_time < 3
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
@@ -2195,18 +2259,21 @@ class TestMaliciousGenerators:
|
||||
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
|
||||
],
|
||||
)
|
||||
def test_duplicate_large_integer_substr_tail(self, opcode):
|
||||
def test_duplicate_large_integer_substr_tail(self, opcode, softfork_height):
|
||||
condition = SINGLE_ARG_INT_SUBSTR_TAIL_COND.format(
|
||||
opcode=opcode.value[0], num=280, val="0xffffffff", filler="0x00"
|
||||
)
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
if softfork_height >= 2000000:
|
||||
assert npc_result.error == error_for_condition(opcode)
|
||||
else:
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
|
||||
print(npc_result.npc_list[0].conditions[0][1])
|
||||
assert ConditionWithArgs(opcode, [int_to_bytes(0xFFFFFFFF)]) in npc_result.npc_list[0].conditions[0][1]
|
||||
print(npc_result.npc_list[0].conditions[0][1])
|
||||
assert ConditionWithArgs(opcode, [int_to_bytes(0xFFFFFFFF)]) in npc_result.npc_list[0].conditions[0][1]
|
||||
assert run_time < 1
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
@@ -2219,10 +2286,10 @@ class TestMaliciousGenerators:
|
||||
ConditionOpcode.ASSERT_SECONDS_RELATIVE,
|
||||
],
|
||||
)
|
||||
def test_duplicate_large_integer_negative(self, opcode):
|
||||
def test_duplicate_large_integer_negative(self, opcode, softfork_height):
|
||||
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0xff")
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
@@ -2230,28 +2297,31 @@ class TestMaliciousGenerators:
|
||||
assert run_time < 2
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
def test_duplicate_reserve_fee(self):
|
||||
def test_duplicate_reserve_fee(self, softfork_height):
|
||||
opcode = ConditionOpcode.RESERVE_FEE
|
||||
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00")
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode.value,
|
||||
[ConditionWithArgs(opcode, [int_to_bytes(100 * 280000)])],
|
||||
)
|
||||
]
|
||||
if softfork_height >= 2000000:
|
||||
assert npc_result.error == error_for_condition(opcode)
|
||||
else:
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
assert npc_result.npc_list[0].conditions == [
|
||||
(
|
||||
opcode.value,
|
||||
[ConditionWithArgs(opcode, [int_to_bytes(100 * 280000)])],
|
||||
)
|
||||
]
|
||||
assert run_time < 2
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
def test_duplicate_reserve_fee_negative(self):
|
||||
def test_duplicate_reserve_fee_negative(self, softfork_height):
|
||||
opcode = ConditionOpcode.RESERVE_FEE
|
||||
condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=200000, val=100, filler="0xff")
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
# RESERVE_FEE conditions fail unconditionally if they have a negative
|
||||
# amount
|
||||
@@ -2263,10 +2333,10 @@ class TestMaliciousGenerators:
|
||||
@pytest.mark.parametrize(
|
||||
"opcode", [ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT]
|
||||
)
|
||||
def test_duplicate_coin_announces(self, opcode):
|
||||
def test_duplicate_coin_announces(self, opcode, softfork_height):
|
||||
condition = CREATE_ANNOUNCE_COND.format(opcode=opcode.value[0], num=5950000)
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
@@ -2276,28 +2346,28 @@ class TestMaliciousGenerators:
|
||||
assert run_time < 21
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
def test_create_coin_duplicates(self):
|
||||
def test_create_coin_duplicates(self, 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
|
||||
# duplicate
|
||||
condition = CREATE_COIN.format(num=600000)
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error == Err.DUPLICATE_OUTPUT.value
|
||||
assert len(npc_result.npc_list) == 0
|
||||
assert run_time < 2
|
||||
print(f"run time:{run_time}")
|
||||
|
||||
def test_many_create_coin(self):
|
||||
def test_many_create_coin(self, softfork_height):
|
||||
# CREATE_COIN
|
||||
# this program will emit many CREATE_COIN conditions, all with different
|
||||
# amounts.
|
||||
# the number 6095 was chosen carefully to not exceed the maximum cost
|
||||
condition = CREATE_UNIQUE_COINS.format(num=6094)
|
||||
start_time = time()
|
||||
npc_result = generator_condition_tester(condition, quote=False)
|
||||
npc_result = generator_condition_tester(condition, quote=False, height=softfork_height)
|
||||
run_time = time() - start_time
|
||||
assert npc_result.error is None
|
||||
assert len(npc_result.npc_list) == 1
|
||||
|
||||
@@ -54,7 +54,7 @@ def large_block_generator(size):
|
||||
|
||||
class TestCostCalculation:
|
||||
@pytest.mark.asyncio
|
||||
async def test_basics(self):
|
||||
async def test_basics(self, softfork_height):
|
||||
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,
|
||||
)
|
||||
|
||||
cost = calculate_cost_of_program(program.program, npc_result, test_constants.COST_PER_BYTE)
|
||||
@@ -111,7 +112,7 @@ class TestCostCalculation:
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mempool_mode(self):
|
||||
async def test_mempool_mode(self, softfork_height):
|
||||
wallet_tool = bt.get_pool_wallet_tool()
|
||||
ph = wallet_tool.get_new_puzzlehash()
|
||||
|
||||
@@ -150,6 +151,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(
|
||||
@@ -157,6 +159,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
|
||||
|
||||
@@ -167,7 +170,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
|
||||
@@ -188,11 +191,12 @@ 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
|
||||
async def test_tx_generator_speed(self):
|
||||
async def test_tx_generator_speed(self, softfork_height):
|
||||
LARGE_BLOCK_COIN_CONSUMED_COUNT = 687
|
||||
generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT)
|
||||
program = SerializedProgram.from_bytes(generator_bytes)
|
||||
@@ -204,6 +208,7 @@ class TestCostCalculation:
|
||||
test_constants.MAX_BLOCK_COST_CLVM,
|
||||
cost_per_byte=test_constants.COST_PER_BYTE,
|
||||
mempool_mode=False,
|
||||
height=softfork_height,
|
||||
)
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
@@ -214,7 +219,7 @@ class TestCostCalculation:
|
||||
assert duration < 1
|
||||
|
||||
@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)
|
||||
@@ -229,14 +234,22 @@ class TestCostCalculation:
|
||||
|
||||
# ensure we fail if the program exceeds the cost
|
||||
generator = BlockGenerator(program, [])
|
||||
npc_result: NPCResult = get_name_puzzle_conditions(generator, 10000000, cost_per_byte=0, mempool_mode=False)
|
||||
npc_result: NPCResult = get_name_puzzle_conditions(
|
||||
generator,
|
||||
10000000,
|
||||
cost_per_byte=0,
|
||||
mempool_mode=False,
|
||||
height=softfork_height,
|
||||
)
|
||||
|
||||
assert npc_result.error is not None
|
||||
assert npc_result.cost == 0
|
||||
|
||||
# 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, 20000000, cost_per_byte=0, mempool_mode=False)
|
||||
npc_result = get_name_puzzle_conditions(
|
||||
generator, 20000000, 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
|
||||
|
||||
@@ -6,10 +6,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 additions_for_npc
|
||||
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] = []
|
||||
@@ -24,6 +25,7 @@ def run_and_get_removals_and_additions(
|
||||
max_cost,
|
||||
cost_per_byte=cost_per_byte,
|
||||
mempool_mode=mempool_mode,
|
||||
height=height,
|
||||
)
|
||||
# build removals list
|
||||
for npc in npc_result.npc_list:
|
||||
|
||||
+24
-6
@@ -41,6 +41,8 @@ from typing import List, TextIO, Tuple, Dict
|
||||
|
||||
import click
|
||||
|
||||
from clvm_rs import COND_CANON_INTS, NO_NEG_DIV
|
||||
|
||||
from chia.consensus.constants import ConsensusConstants
|
||||
from chia.consensus.default_constants import DEFAULT_CONSTANTS
|
||||
from chia.types.blockchain_format.program import SerializedProgram
|
||||
@@ -87,9 +89,18 @@ def npc_to_dict(npc: NPC):
|
||||
}
|
||||
|
||||
|
||||
def run_generator(block_generator: BlockGenerator, constants: ConsensusConstants, max_cost: int) -> List[CAT]:
|
||||
def run_generator(
|
||||
block_generator: BlockGenerator, constants: ConsensusConstants, max_cost: int, height: uint32
|
||||
) -> List[CAT]:
|
||||
|
||||
if height >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT:
|
||||
# conditions must use integers in canonical encoding (i.e. no redundant
|
||||
# leading zeros)
|
||||
# the division operator may not be used with negative operands
|
||||
flags = COND_CANON_INTS | NO_NEG_DIV
|
||||
else:
|
||||
flags = 0
|
||||
|
||||
flags = 0
|
||||
_, result = block_generator.program.run_with_cost(max_cost, flags, block_generator.generator_refs())
|
||||
|
||||
coin_spends = result.first()
|
||||
@@ -171,17 +182,23 @@ def run_full_block(block: FullBlock, constants: ConsensusConstants) -> List[CAT]
|
||||
if block.transactions_generator is None or block.transactions_info is None:
|
||||
raise RuntimeError("transactions_generator of FullBlock is null")
|
||||
block_generator = BlockGenerator(block.transactions_generator, generator_args)
|
||||
return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost))
|
||||
return run_generator(
|
||||
block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), block.height
|
||||
)
|
||||
|
||||
|
||||
def run_generator_with_args(
|
||||
generator_program_hex: str, generator_args: List[GeneratorArg], constants: ConsensusConstants, cost: uint64
|
||||
generator_program_hex: str,
|
||||
generator_args: List[GeneratorArg],
|
||||
constants: ConsensusConstants,
|
||||
cost: uint64,
|
||||
height: uint32,
|
||||
) -> List[CAT]:
|
||||
if not generator_program_hex:
|
||||
return []
|
||||
generator_program = SerializedProgram.fromhex(generator_program_hex)
|
||||
block_generator = BlockGenerator(generator_program, generator_args)
|
||||
return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, cost))
|
||||
return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, cost), height)
|
||||
|
||||
|
||||
@click.command()
|
||||
@@ -195,11 +212,12 @@ def run_json_block(full_block, constants: ConsensusConstants) -> List[CAT]:
|
||||
ref_list = full_block["block"]["transactions_generator_ref_list"]
|
||||
tx_info: dict = full_block["block"]["transactions_info"]
|
||||
generator_program_hex: str = full_block["block"]["transactions_generator"]
|
||||
height = full_block["block"]["reward_chain_block"]["height"]
|
||||
cat_list: List[CAT] = []
|
||||
if tx_info and generator_program_hex:
|
||||
cost = tx_info["cost"]
|
||||
args = ref_list_to_args(ref_list)
|
||||
cat_list = run_generator_with_args(generator_program_hex, args, constants, cost)
|
||||
cat_list = run_generator_with_args(generator_program_hex, args, constants, cost, height)
|
||||
|
||||
return cat_list
|
||||
|
||||
|
||||
Reference in New Issue
Block a user