More tests (#2295)

Co-authored-by: Mariano <sorgente711@gmail.com>
This commit is contained in:
Arvid Norberg 2021-04-21 20:17:21 +02:00 committed by GitHub
parent c5595e280c
commit e55988479b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 615 additions and 96 deletions

View File

@ -112,7 +112,7 @@ async def print_balances(args: dict, wallet_client: WalletRpcClient, fingerprint
if typ != "STANDARD_WALLET":
print(f"Wallet ID {wallet_id} type {typ} {summary['name']}")
print(f" -Confirmed: " f"{balances['confirmed_wallet_balance']/units['colouredcoin']}")
print(f" -Unconfirmed: {balances['unconfirmed_wallet_balance']/units['colouredcoin']}")
print(f" -Confirmed - Pending Outgoing: {balances['unconfirmed_wallet_balance']/units['colouredcoin']}")
print(f" -Spendable: {balances['spendable_balance']/units['colouredcoin']}")
print(f" -Pending change: {balances['pending_change']/units['colouredcoin']}")
else:

View File

@ -244,6 +244,7 @@ async def validate_block_body(
additions_dic: Dict[bytes32, Coin] = {}
# 10. Check additions for max coin amount
# Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer
# We will not even reach here because Coins do type checking (uint64)
for coin in additions + coinbase_additions:
additions_dic[coin.name()] = coin
if coin.amount > constants.MAX_COIN_AMOUNT:
@ -287,6 +288,7 @@ async def validate_block_body(
return Err.DOUBLE_SPEND, None
# 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block)
# The fork point is the last block in common between the peak chain and the chain of `block`
if peak is None or height == 0:
fork_h: int = -1
elif fork_point_with_peak is not None:
@ -294,30 +296,27 @@ async def validate_block_body(
else:
fork_h = find_fork_point_in_chain(blocks, peak, blocks.block_record(block.prev_header_hash))
if fork_h == -1:
coin_store_reorg_height = -1
else:
last_block_in_common = await blocks.get_block_record_from_db(blocks.height_to_hash(uint32(fork_h)))
assert last_block_in_common is not None
coin_store_reorg_height = last_block_in_common.height
# Get additions and removals since (after) fork_h but not including this block
additions_since_fork: Dict[bytes32, Tuple[Coin, uint32]] = {}
# The values include: the coin that was added, the height of the block in which it was confirmed, and the
# timestamp of the block in which it was confirmed
additions_since_fork: Dict[bytes32, Tuple[Coin, uint32, uint64]] = {} # This includes coinbase additions
removals_since_fork: Set[bytes32] = set()
coinbases_since_fork: Dict[bytes32, uint32] = {}
# For height 0, there are no additions and removals before this block, so we can skip
if height > 0:
# First, get all the blocks in the fork > fork_h, < block.height
prev_block: Optional[FullBlock] = await block_store.get_full_block(block.prev_header_hash)
reorg_blocks: Dict[int, FullBlock] = {}
reorg_blocks: Dict[uint32, FullBlock] = {}
curr: Optional[FullBlock] = prev_block
assert curr is not None
reorg_blocks[curr.height] = curr
while curr.height > fork_h:
if curr.height == 0:
break
curr = await block_store.get_full_block(curr.prev_header_hash)
assert curr is not None
reorg_blocks[curr.height] = curr
if fork_h != -1:
assert len(reorg_blocks) == height - fork_h - 1
curr = prev_block
assert curr is not None
@ -326,9 +325,9 @@ async def validate_block_body(
if curr.transactions_generator is not None:
# These blocks are in the past and therefore assumed to be valid, so get_block_generator won't raise
curr_block_generator: Optional[BlockGenerator] = await get_block_generator(curr)
assert curr_block_generator is not None
assert curr_block_generator is not None and curr.transactions_info is not None
curr_npc_result = get_name_puzzle_conditions(
curr_block_generator, constants.MAX_BLOCK_COST_CLVM, False
curr_block_generator, min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost), False
)
removals_in_curr, additions_in_curr = tx_removals_and_additions(curr_npc_result.npc_list)
else:
@ -336,13 +335,21 @@ async def validate_block_body(
additions_in_curr = []
for c_name in removals_in_curr:
assert c_name not in removals_since_fork
removals_since_fork.add(c_name)
for c in additions_in_curr:
additions_since_fork[c.name()] = (c, curr.height)
assert c.name() not in additions_since_fork
assert curr.foliage_transaction_block is not None
additions_since_fork[c.name()] = (c, curr.height, curr.foliage_transaction_block.timestamp)
for coinbase_coin in curr.get_included_reward_coins():
additions_since_fork[coinbase_coin.name()] = (coinbase_coin, curr.height)
coinbases_since_fork[coinbase_coin.name()] = curr.height
assert coinbase_coin.name() not in additions_since_fork
assert curr.foliage_transaction_block is not None
additions_since_fork[coinbase_coin.name()] = (
coinbase_coin,
curr.height,
curr.foliage_transaction_block.timestamp,
)
if curr.height == 0:
break
curr = reorg_blocks[curr.height - 1]
@ -356,18 +363,18 @@ async def validate_block_body(
new_unspent: CoinRecord = CoinRecord(
rem_coin,
height,
uint32(0),
height,
True,
False,
(rem in coinbases_since_fork),
block.foliage_transaction_block.timestamp,
)
removal_coin_records[new_unspent.name] = new_unspent
else:
unspent = await coin_store.get_coin_record(rem)
if unspent is not None and unspent.confirmed_block_index <= coin_store_reorg_height:
if unspent is not None and unspent.confirmed_block_index <= fork_h:
# Spending something in the current chain, confirmed before fork
# (We ignore all coins confirmed after fork)
if unspent.spent == 1 and unspent.spent_block_index <= coin_store_reorg_height:
if unspent.spent == 1 and unspent.spent_block_index <= fork_h:
# Check for coins spent in an ancestor block
return Err.DOUBLE_SPEND, None
removal_coin_records[unspent.name] = unspent
@ -376,22 +383,22 @@ async def validate_block_body(
if rem not in additions_since_fork:
# Check for spending a coin that does not exist in this fork
return Err.UNKNOWN_UNSPENT, None
new_coin, confirmed_height = additions_since_fork[rem]
new_coin, confirmed_height, confirmed_timestamp = additions_since_fork[rem]
new_coin_record: CoinRecord = CoinRecord(
new_coin,
confirmed_height,
uint32(0),
False,
(rem in coinbases_since_fork),
block.foliage_transaction_block.timestamp,
False,
confirmed_timestamp,
)
removal_coin_records[new_coin_record.name] = new_coin_record
# This check applies to both coins created before fork (pulled from coin_store),
# and coins created after fork (additions_since_fork)>
# and coins created after fork (additions_since_fork)
if rem in removals_since_fork:
# This coin was spent in the fork
return Err.DOUBLE_SPEND, None
return Err.DOUBLE_SPEND_IN_FORK, None
removed = 0
for unspent in removal_coin_records.values():
@ -422,8 +429,8 @@ async def validate_block_body(
if fees < assert_fee_sum:
return Err.RESERVE_FEE_CONDITION_FAILED, None
# 18. Check that the assert fee amount < maximum coin amount
if fees > constants.MAX_COIN_AMOUNT:
# 18. Check that the fee amount + farmer reward < maximum coin amount
if fees + calculate_base_farmer_reward(height) > constants.MAX_COIN_AMOUNT:
return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None
# 19. Check that the computed fees are equal to the fees in the block header

View File

@ -1,3 +1,4 @@
import logging
import random
from dataclasses import replace
from typing import Callable, Dict, List, Optional, Tuple
@ -31,6 +32,8 @@ from chia.util.merkle_set import MerkleSet
from chia.util.prev_transaction_block import get_prev_transaction_block
from chia.util.recursive_replace import recursive_replace
log = logging.getLogger(__name__)
def create_foliage(
constants: ConsensusConstants,

View File

@ -201,8 +201,10 @@ class Blockchain(BlockchainInterface):
block_generator: Optional[BlockGenerator] = await self.get_block_generator(block)
except ValueError:
return ReceiveBlockResult.INVALID_BLOCK, Err.GENERATOR_REF_HAS_NO_GENERATOR, None
assert block_generator is not None
npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False)
assert block_generator is not None and block.transactions_info is not None
npc_result = get_name_puzzle_conditions(
block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False
)
removals, additions = block_removals_and_additions(block, npc_result.npc_list)
else:
removals, additions = [], list(block.get_included_reward_coins())

View File

@ -79,9 +79,6 @@ class CoinStore:
)
await self._add_coin_record(record)
for coin_name in removals:
await self._set_spent(coin_name, block.height)
included_reward_coins = block.get_included_reward_coins()
if block.height == 0:
assert len(included_reward_coins) == 0
@ -99,6 +96,9 @@ class CoinStore:
)
await self._add_coin_record(reward_coin_r)
for coin_name in removals:
await self._set_spent(coin_name, block.height)
# Checks DB and DiffStores for CoinRecord with coin_name and returns it
async def get_coin_record(self, coin_name: bytes32) -> Optional[CoinRecord]:
if coin_name.hex() in self.coin_record_cache:
@ -215,7 +215,7 @@ class CoinStore:
async def _set_spent(self, coin_name: bytes32, index: uint32):
current: Optional[CoinRecord] = await self.get_coin_record(coin_name)
if current is None:
return
raise ValueError(f"Cannot spend a coin that does not exist in db: {coin_name}")
spent: CoinRecord = CoinRecord(
current.coin,
current.confirmed_block_index,

View File

@ -19,7 +19,7 @@ class CoinRecord(Streamable):
spent_block_index: uint32
spent: bool
coinbase: bool
timestamp: uint64
timestamp: uint64 # Timestamp of the block at height confirmed_block_index
@property
def name(self) -> bytes32:

View File

@ -146,6 +146,7 @@ class Err(Enum):
INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT = 119
FUTURE_GENERATOR_REFS = 120 # All refs must be to blocks in the past
GENERATOR_REF_HAS_NO_GENERATOR = 121
DOUBLE_SPEND_IN_FORK = 122
class ValidationError(Exception):

View File

@ -137,18 +137,15 @@ class WalletTool:
self,
amount: uint64,
new_puzzle_hash: bytes32,
coin: Coin,
coins: List[Coin],
condition_dic: Dict[ConditionOpcode, List[ConditionWithArgs]],
fee: int = 0,
secret_key: Optional[PrivateKey] = None,
) -> List[CoinSolution]:
spends = []
spend_value = coin.amount
puzzle_hash = coin.puzzle_hash
if secret_key is None:
secret_key = self.get_private_key_for_puzzle_hash(puzzle_hash)
pubkey = secret_key.get_g1()
puzzle = puzzle_for_pk(bytes(pubkey))
spend_value = sum([c.amount for c in coins])
if ConditionOpcode.CREATE_COIN not in condition_dic:
condition_dic[ConditionOpcode.CREATE_COIN] = []
@ -160,11 +157,20 @@ class WalletTool:
change_puzzle_hash = self.get_new_puzzlehash()
change_output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [change_puzzle_hash, int_to_bytes(change)])
condition_dic[output.opcode].append(change_output)
solution = self.make_solution(condition_dic)
main_solution: Program = self.make_solution(condition_dic)
else:
solution = self.make_solution(condition_dic)
main_solution = self.make_solution(condition_dic)
spends.append(CoinSolution(coin, puzzle, solution))
for n, coin in enumerate(coins):
puzzle_hash = coin.puzzle_hash
if secret_key is None:
secret_key = self.get_private_key_for_puzzle_hash(puzzle_hash)
pubkey = secret_key.get_g1()
puzzle = puzzle_for_pk(bytes(pubkey))
if n == 0:
spends.append(CoinSolution(coin, puzzle, main_solution))
else:
spends.append(CoinSolution(coin, puzzle, self.make_solution({})))
return spends
def sign_transaction(self, coin_solutions: List[CoinSolution]) -> SpendBundle:
@ -200,6 +206,20 @@ class WalletTool:
) -> SpendBundle:
if condition_dic is None:
condition_dic = {}
transaction = self.generate_unsigned_transaction(amount, new_puzzle_hash, coin, condition_dic, fee)
transaction = self.generate_unsigned_transaction(amount, new_puzzle_hash, [coin], condition_dic, fee)
assert transaction is not None
return self.sign_transaction(transaction)
def generate_signed_transaction_multiple_coins(
self,
amount: uint64,
new_puzzle_hash: bytes32,
coins: List[Coin],
condition_dic: Dict[ConditionOpcode, List[ConditionWithArgs]] = None,
fee: int = 0,
) -> SpendBundle:
if condition_dic is None:
condition_dic = {}
transaction = self.generate_unsigned_transaction(amount, new_puzzle_hash, coins, condition_dic, fee)
assert transaction is not None
return self.sign_transaction(transaction)

View File

@ -319,6 +319,12 @@ class Wallet:
spends: List[CoinSolution] = []
output_created = False
# Check for duplicates
if primaries is not None:
all_primaries_list = [(p["puzzlehash"], p["amount"]) for p in primaries] + [(newpuzzlehash, amount)]
if len(set(all_primaries_list)) != len(all_primaries_list):
raise ValueError("Cannot create two identical coins")
for coin in coins:
self.log.info(f"coin from coins {coin}")
puzzle: Program = await self.puzzle_for_puzzle_hash(coin.puzzle_hash)

View File

@ -5,21 +5,25 @@ import multiprocessing
import time
from dataclasses import replace
from secrets import token_bytes
from typing import Optional
import pytest
from blspy import AugSchemeMPL, G2Element
from clvm.casts import int_to_bytes
from chia.consensus.block_rewards import calculate_base_farmer_reward
from chia.consensus.blockchain import ReceiveBlockResult
from chia.consensus.coinbase import create_farmer_coin
from chia.consensus.pot_iterations import is_overflow_block
from chia.full_node.bundle_tools import detect_potential_template_generator
from chia.types.blockchain_format.classgroup import ClassgroupElement
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.foliage import TransactionsInfo, FoliageTransactionBlock
from chia.types.blockchain_format.foliage import TransactionsInfo
from chia.types.blockchain_format.program import SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.blockchain_format.slots import InfusedChallengeChainSubSlot
from chia.types.blockchain_format.vdf import VDFInfo, VDFProof
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.end_of_slot_bundle import EndOfSubSlotBundle
from chia.types.full_block import FullBlock
from chia.types.spend_bundle import SpendBundle
@ -28,6 +32,7 @@ from chia.util.block_tools import BlockTools, get_vdf_info_and_proof
from chia.util.errors import Err
from chia.util.hash import std_hash
from chia.util.ints import uint8, uint64, uint32
from chia.util.merkle_set import MerkleSet
from chia.util.recursive_replace import recursive_replace
from chia.util.wallet_tools import WalletTool
from tests.core.fixtures import default_400_blocks # noqa: F401; noqa: F401
@ -1543,6 +1548,54 @@ class TestBlockHeaderValidation:
assert (await empty_blockchain.receive_block(blocks[-1]))[0] == ReceiveBlockResult.NEW_PEAK
class TestPreValidation:
@pytest.mark.asyncio
async def test_pre_validation_fails_bad_blocks(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(2)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
block_bad = recursive_replace(
blocks[-1], "reward_chain_block.total_iters", blocks[-1].reward_chain_block.total_iters + 1
)
res = await empty_blockchain.pre_validate_blocks_multiprocessing([blocks[0], block_bad], {})
assert res[0].error is None
assert res[1].error is not None
@pytest.mark.asyncio
async def test_pre_validation(self, empty_blockchain, default_1000_blocks):
blocks = default_1000_blocks[:100]
start = time.time()
n_at_a_time = min(multiprocessing.cpu_count(), 32)
times_pv = []
times_rb = []
for i in range(0, len(blocks), n_at_a_time):
end_i = min(i + n_at_a_time, len(blocks))
blocks_to_validate = blocks[i:end_i]
start_pv = time.time()
res = await empty_blockchain.pre_validate_blocks_multiprocessing(blocks_to_validate, {})
end_pv = time.time()
times_pv.append(end_pv - start_pv)
assert res is not None
for n in range(end_i - i):
assert res[n] is not None
assert res[n].error is None
block = blocks_to_validate[n]
start_rb = time.time()
result, err, _ = await empty_blockchain.receive_block(block, res[n])
end_rb = time.time()
times_rb.append(end_rb - start_rb)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
log.info(
f"Added block {block.height} total iters {block.total_iters} "
f"new slot? {len(block.finished_sub_slots)}, time {end_rb - start_rb}"
)
end = time.time()
log.info(f"Total time: {end - start} seconds")
log.info(f"Average pv: {sum(times_pv)/(len(blocks)/n_at_a_time)}")
log.info(f"Average rb: {sum(times_rb)/(len(blocks))}")
class TestBodyValidation:
@pytest.mark.asyncio
async def test_not_tx_block_but_has_data(self, empty_blockchain):
@ -1866,6 +1919,481 @@ class TestBodyValidation:
assert err == Err.GENERATOR_REF_HAS_NO_GENERATOR or err == Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT
assert (await b.pre_validate_blocks_multiprocessing([block_2], {})) is None
@pytest.mark.asyncio
async def test_cost_exceeds_max(self, empty_blockchain):
# 7
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
condition_dict = {ConditionOpcode.CREATE_COIN: []}
for i in range(7000):
output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [bt.pool_ph, int_to_bytes(i)])
condition_dict[ConditionOpcode.CREATE_COIN].append(output)
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0], condition_dic=condition_dict
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
assert (await b.receive_block(blocks[-1]))[1] == Err.BLOCK_COST_EXCEEDS_MAX
@pytest.mark.asyncio
async def test_clvm_must_not_fail(self, empty_blockchain):
# 8
pass
@pytest.mark.asyncio
async def test_invalid_cost_in_block(self, empty_blockchain):
# 9
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
block: FullBlock = blocks[-1]
# zero
block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(0))
block_2 = recursive_replace(
block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash()
)
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.INVALID_BLOCK_COST
# too low
block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(1))
block_2 = recursive_replace(
block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash()
)
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.GENERATOR_RUNTIME_ERROR
# too high
block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(1000000))
block_2 = recursive_replace(
block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash()
)
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.INVALID_BLOCK_COST
err = (await b.receive_block(block))[1]
assert err is None
@pytest.mark.asyncio
async def test_max_coin_amount(self):
# 10
# TODO: fix, this is not reaching validation. Because we can't create a block with such amounts due to uint64
# limit in Coin
new_test_constants = test_constants.replace(
**{"GENESIS_PRE_FARM_POOL_PUZZLE_HASH": bt.pool_ph, "GENESIS_PRE_FARM_FARMER_PUZZLE_HASH": bt.pool_ph}
)
b, connection, db_path = await create_blockchain(new_test_constants)
bt_2 = BlockTools(new_test_constants)
bt_2.constants = bt_2.constants.replace(
**{"GENESIS_PRE_FARM_POOL_PUZZLE_HASH": bt.pool_ph, "GENESIS_PRE_FARM_FARMER_PUZZLE_HASH": bt.pool_ph}
)
blocks = bt_2.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt_2.get_pool_wallet_tool()
condition_dict = {ConditionOpcode.CREATE_COIN: []}
output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [bt_2.pool_ph, int_to_bytes(2 ** 64)])
condition_dict[ConditionOpcode.CREATE_COIN].append(output)
tx: SpendBundle = wt.generate_signed_transaction_multiple_coins(
10,
wt.get_new_puzzlehash(),
list(blocks[1].get_included_reward_coins()),
condition_dic=condition_dict,
)
try:
blocks = bt_2.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
assert False
except Exception as e:
pass
await connection.close()
b.shut_down()
db_path.unlink()
@pytest.mark.asyncio
async def test_invalid_merkle_roots(self, empty_blockchain):
# 11
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
block: FullBlock = blocks[-1]
merkle_set = MerkleSet()
# additions
block_2 = recursive_replace(block, "foliage_transaction_block.additions_root", merkle_set.get_root())
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.BAD_ADDITION_ROOT
# removals
merkle_set.add_already_hashed(std_hash(b"1"))
block_2 = recursive_replace(block, "foliage_transaction_block.removals_root", merkle_set.get_root())
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.BAD_REMOVAL_ROOT
@pytest.mark.asyncio
async def test_invalid_filter(self, empty_blockchain):
# 12
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
block: FullBlock = blocks[-1]
block_2 = recursive_replace(block, "foliage_transaction_block.filter_hash", std_hash(b"3"))
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.INVALID_TRANSACTIONS_FILTER_HASH
@pytest.mark.asyncio
async def test_duplicate_outputs(self, empty_blockchain):
# 13
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
condition_dict = {ConditionOpcode.CREATE_COIN: []}
for i in range(2):
output = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [bt.pool_ph, int_to_bytes(1)])
condition_dict[ConditionOpcode.CREATE_COIN].append(output)
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0], condition_dic=condition_dict
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
assert (await b.receive_block(blocks[-1]))[1] == Err.DUPLICATE_OUTPUT
@pytest.mark.asyncio
async def test_duplicate_removals(self, empty_blockchain):
# 14
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
tx_2: SpendBundle = wt.generate_signed_transaction(
11, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
agg = SpendBundle.aggregate([tx, tx_2])
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=agg
)
assert (await b.receive_block(blocks[-1]))[1] == Err.DOUBLE_SPEND
@pytest.mark.asyncio
async def test_double_spent_in_coin_store(self, empty_blockchain):
# 15
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
assert (await b.receive_block(blocks[-1]))[0] == ReceiveBlockResult.NEW_PEAK
tx_2: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-2].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx_2
)
assert (await b.receive_block(blocks[-1]))[1] == Err.DOUBLE_SPEND
@pytest.mark.asyncio
async def test_double_spent_in_reorg(self, empty_blockchain):
# 15
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
assert (await b.receive_block(blocks[-1]))[0] == ReceiveBlockResult.NEW_PEAK
new_coin: Coin = tx.additions()[0]
tx_2: SpendBundle = wt.generate_signed_transaction(10, wt.get_new_puzzlehash(), new_coin)
# This is fine because coin exists
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx_2
)
assert (await b.receive_block(blocks[-1]))[0] == ReceiveBlockResult.NEW_PEAK
blocks = bt.get_consecutive_blocks(5, block_list_input=blocks, guarantee_transaction_block=True)
for block in blocks[-5:]:
assert (await b.receive_block(block))[0] == ReceiveBlockResult.NEW_PEAK
blocks_reorg = bt.get_consecutive_blocks(2, block_list_input=blocks[:-7], guarantee_transaction_block=True)
assert (await b.receive_block(blocks_reorg[-2]))[0] == ReceiveBlockResult.ADDED_AS_ORPHAN
assert (await b.receive_block(blocks_reorg[-1]))[0] == ReceiveBlockResult.ADDED_AS_ORPHAN
# Coin does not exist in reorg
blocks_reorg = bt.get_consecutive_blocks(
1, block_list_input=blocks_reorg, guarantee_transaction_block=True, transaction_data=tx_2
)
assert (await b.receive_block(blocks_reorg[-1]))[1] == Err.UNKNOWN_UNSPENT
# Finally add the block to the fork (spending both in same bundle, this is ephemeral)
agg = SpendBundle.aggregate([tx, tx_2])
blocks_reorg = bt.get_consecutive_blocks(
1, block_list_input=blocks_reorg[:-1], guarantee_transaction_block=True, transaction_data=agg
)
assert (await b.receive_block(blocks_reorg[-1]))[1] is None
blocks_reorg = bt.get_consecutive_blocks(
1, block_list_input=blocks_reorg, guarantee_transaction_block=True, transaction_data=tx_2
)
assert (await b.receive_block(blocks_reorg[-1]))[1] == Err.DOUBLE_SPEND_IN_FORK
rewards_ph = wt.get_new_puzzlehash()
blocks_reorg = bt.get_consecutive_blocks(
10,
block_list_input=blocks_reorg[:-1],
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=rewards_ph,
)
for block in blocks_reorg[-10:]:
r, e, _ = await b.receive_block(block)
assert e is None
# ephemeral coin is spent
first_coin = await b.coin_store.get_coin_record(new_coin.name())
assert first_coin is not None and first_coin.spent
second_coin = await b.coin_store.get_coin_record(tx_2.additions()[0].name())
assert second_coin is not None and not second_coin.spent
farmer_coin = create_farmer_coin(
blocks_reorg[-1].height,
rewards_ph,
calculate_base_farmer_reward(blocks_reorg[-1].height),
bt.constants.GENESIS_CHALLENGE,
)
tx_3: SpendBundle = wt.generate_signed_transaction(10, wt.get_new_puzzlehash(), farmer_coin)
blocks_reorg = bt.get_consecutive_blocks(
1, block_list_input=blocks_reorg, guarantee_transaction_block=True, transaction_data=tx_3
)
assert (await b.receive_block(blocks_reorg[-1]))[1] is None
farmer_coin = await b.coin_store.get_coin_record(farmer_coin.name())
assert first_coin is not None and farmer_coin.spent
@pytest.mark.asyncio
async def test_minting_coin(self, empty_blockchain):
# 16 TODO
# 17 is tested in mempool tests
pass
@pytest.mark.asyncio
async def test_max_coin_amount_fee(self):
# 18 TODO: we can't create a block with such amounts due to uint64
pass
@pytest.mark.asyncio
async def test_invalid_fees_in_block(self, empty_blockchain):
# 19
b = empty_blockchain
blocks = bt.get_consecutive_blocks(
3,
guarantee_transaction_block=True,
farmer_reward_puzzle_hash=bt.pool_ph,
pool_reward_puzzle_hash=bt.pool_ph,
)
assert (await b.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[1]))[0] == ReceiveBlockResult.NEW_PEAK
assert (await b.receive_block(blocks[2]))[0] == ReceiveBlockResult.NEW_PEAK
wt: WalletTool = bt.get_pool_wallet_tool()
tx: SpendBundle = wt.generate_signed_transaction(
10, wt.get_new_puzzlehash(), list(blocks[-1].get_included_reward_coins())[0]
)
blocks = bt.get_consecutive_blocks(
1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx
)
block: FullBlock = blocks[-1]
# wrong feees
block_2: FullBlock = recursive_replace(block, "transactions_info.fees", uint64(1239))
block_2 = recursive_replace(
block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash()
)
block_2 = recursive_replace(
block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash()
)
new_m = block_2.foliage.foliage_transaction_block_hash
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.INVALID_BLOCK_FEE_AMOUNT
class TestReorgs:
@pytest.mark.asyncio
@ -2026,51 +2554,3 @@ class TestReorgs:
for block in blocks_fork:
result, error_code, _ = await b.receive_block(block)
assert error_code is None
class TestPreValidation:
@pytest.mark.asyncio
async def test_pre_validation_fails_bad_blocks(self, empty_blockchain):
blocks = bt.get_consecutive_blocks(2)
assert (await empty_blockchain.receive_block(blocks[0]))[0] == ReceiveBlockResult.NEW_PEAK
block_bad = recursive_replace(
blocks[-1], "reward_chain_block.total_iters", blocks[-1].reward_chain_block.total_iters + 1
)
res = await empty_blockchain.pre_validate_blocks_multiprocessing([blocks[0], block_bad], {})
assert res[0].error is None
assert res[1].error is not None
@pytest.mark.asyncio
async def test_pre_validation(self, empty_blockchain, default_1000_blocks):
blocks = default_1000_blocks[:100]
start = time.time()
n_at_a_time = min(multiprocessing.cpu_count(), 32)
times_pv = []
times_rb = []
for i in range(0, len(blocks), n_at_a_time):
end_i = min(i + n_at_a_time, len(blocks))
blocks_to_validate = blocks[i:end_i]
start_pv = time.time()
res = await empty_blockchain.pre_validate_blocks_multiprocessing(blocks_to_validate, {})
end_pv = time.time()
times_pv.append(end_pv - start_pv)
assert res is not None
for n in range(end_i - i):
assert res[n] is not None
assert res[n].error is None
block = blocks_to_validate[n]
start_rb = time.time()
result, err, _ = await empty_blockchain.receive_block(block, res[n])
end_rb = time.time()
times_rb.append(end_rb - start_rb)
assert err is None
assert result == ReceiveBlockResult.NEW_PEAK
log.info(
f"Added block {block.height} total iters {block.total_iters} "
f"new slot? {len(block.finished_sub_slots)}, time {end_rb - start_rb}"
)
end = time.time()
log.info(f"Total time: {end - start} seconds")
log.info(f"Average pv: {sum(times_pv)/(len(blocks)/n_at_a_time)}")
log.info(f"Average rb: {sum(times_rb)/(len(blocks))}")