def fill_block(chain, from_, key, gas, data): if not isinstance(chain, MiningChain): pytest.skip( "Cannot fill block automatically unless using a MiningChain") return recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c') amount = 100 vm = chain.get_vm() assert vm.get_header().gas_used == 0 while True: tx = new_transaction(chain.get_vm(), from_, recipient, amount, key, gas=gas, data=data) try: chain.apply_transaction(tx) except ValidationError as exc: if str(exc).startswith("Transaction exceeds gas limit"): break else: raise exc else: new_header = chain.get_vm().get_block().header assert new_header.gas_used > 0 assert new_header.gas_used <= new_header.gas_limit assert chain.get_vm().get_block().header.gas_used > 0
def test_base_fee_evolution(london_plus_miner, funded_address, funded_address_private_key, num_txns, expected_base_fee): chain = london_plus_miner assert chain.header.gas_limit == FOUR_TXN_GAS_LIMIT vm = chain.get_vm() txns = [ new_transaction( vm, funded_address, b'\x00' * 20, private_key=funded_address_private_key, gas=21000, nonce=nonce, ) for nonce in range(num_txns) ] block_import, _, _ = chain.mine_all(txns, gas_limit=FOUR_TXN_GAS_LIMIT) mined_header = block_import.imported_block.header assert mined_header.gas_limit == FOUR_TXN_GAS_LIMIT assert mined_header.gas_used == 21000 * num_txns assert mined_header.base_fee_per_gas == 10**9 # Initialize at 1 gwei block_import, _, _ = chain.mine_all([], gas_limit=FOUR_TXN_GAS_LIMIT) mined_header = block_import.imported_block.header assert mined_header.gas_limit == FOUR_TXN_GAS_LIMIT assert mined_header.gas_used == 0 # Check that the base fee evolved correctly, depending on how much gas was used in the parent assert mined_header.base_fee_per_gas == expected_base_fee
def deploy_dos_contract(self, chain: MiningChain) -> None: # Instantiate the contract dos_contract = self.w3.eth.contract( abi=self.contract_interface['abi'], bytecode=self.contract_interface['bin']) # Build transaction to deploy the contract w3_tx1 = dos_contract.constructor().buildTransaction(W3_TX_DEFAULTS) tx = new_transaction( vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=CREATE_CONTRACT_ADDRESS, amount=0, gas=FIRST_TX_GAS_LIMIT, data=decode_hex(w3_tx1['data']), ) logging.debug(f'Applying Transaction {tx}') block, receipt, computation = chain.apply_transaction(tx) self.deployed_contract_address = computation.msg.storage_address computation.raise_if_error() # Interact with the deployed contract by calling the totalSupply() API ????? self.dos_contract = self.w3.eth.contract( address=Web3.toChecksumAddress( encode_hex(self.deployed_contract_address)), abi=self.contract_interface['abi'], )
def test_import_block(chain, funded_address, funded_address_private_key): recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c') amount = 100 from_ = funded_address tx = new_transaction(chain.get_vm(), from_, recipient, amount, funded_address_private_key) if isinstance(chain, MiningChain): # Can use the mining chain functionality to build transactions in-flight pending_header = chain.header new_block, _, computation = chain.apply_transaction(tx) else: # Have to manually build the block for the import_block test new_block, _, computations = chain.build_block_with_transactions([tx]) computation = computations[0] # Generate the pending header to import the new block on pending_header = chain.create_header_from_parent( chain.get_canonical_head()) assert not computation.is_error # import the built block validation_vm = chain.get_vm(pending_header) block, _ = validation_vm.import_block(new_block) assert block.transactions == (tx, )
def _erc_transfer_from(self, addr1: str, addr2: str, chain: MiningChain, nonce: int = None) -> None: w3_tx = self.simple_token.functions.transferFrom( addr1, addr2, TRANSER_FROM_AMOUNT).buildTransaction(W3_TX_DEFAULTS) tx = new_transaction( vm=chain.get_vm(), private_key=SECOND_ADDRESS_PRIVATE_KEY, from_=SECOND_ADDRESS, to=self.deployed_contract_address, amount=0, gas=SECOND_TX_GAS_LIMIT, data=decode_hex(w3_tx['data']), nonce=nonce, ) def callback(receipt, computation) -> None: computation.raise_if_error() assert computation.output == b'\0' * 31 + b'\x01', computation.output return tx, callback
def _deploy_simple_token(self, chain: MiningChain, nonce: int = None) -> None: # Instantiate the contract SimpleToken = self.w3.eth.contract( abi=self.contract_interface['abi'], bytecode=self.contract_interface['bin']) # Build transaction to deploy the contract w3_tx = SimpleToken.constructor().buildTransaction(W3_TX_DEFAULTS) tx = new_transaction( vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=CREATE_CONTRACT_ADDRESS, amount=0, gas=FIRST_TX_GAS_LIMIT, data=decode_hex(w3_tx['data']), nonce=nonce, ) def callback(receipt, computation) -> None: computation.raise_if_error() # Keep track of deployed contract address self.deployed_contract_address = computation.msg.storage_address # Keep track of simple_token object self.simple_token = self.w3.eth.contract( address=Web3.toChecksumAddress( encode_hex(self.deployed_contract_address)), abi=self.contract_interface['abi'], ) return tx, callback
def test_import_block(paragon_chain, clique): vm = paragon_chain.get_vm() tx = new_transaction(vm, ALICE, BOB, 10, ALICE_PK) assert vm.state.get_balance(ALICE) == ALICE_INITIAL_BALANCE assert vm.state.get_balance(BOB) == BOB_INITIAL_BALANCE assert vm.state.get_balance(vm.get_block().header.coinbase) == 0 signed_header = sign_block_header( vm.get_block().header.copy( extra_data=VANITY_LENGTH * b'0' + SIGNATURE_LENGTH * b'0', state_root= b'\x99\xaa\xf5CF^\x95_\xce~\xe4)\x00\xb1zr\x1dr\xd6\x00N^\xa6\xdc\xc41\x90~\xb7te\x00', # noqa: E501 transaction_root= b'\xd1\t\xc4\x150\x9f\xb0\xb4H{\xfd$?Q\x16\x90\xac\xb2L[f\x98\xdd\xc6*\xf7\n\x84f\xafg\xb3', # noqa: E501 nonce=NONCE_DROP, gas_used=21000, difficulty=2, receipt_root= b'\x05k#\xfb\xbaH\x06\x96\xb6_\xe5\xa5\x9b\x8f!H\xa1)\x91\x03\xc4\xf5}\xf89#:\xf2\xcfL\xa2\xd2' # noqa: E501 ), ALICE_PK) block = vm.get_block_class()(header=signed_header, transactions=[tx]) paragon_chain.import_block(block) # Alice new balance is old balance - 10 + 21000 tx fee (she's the signer) assert paragon_chain.get_vm().state.get_balance(ALICE) == 20999990 assert paragon_chain.get_vm().state.get_balance(BOB) == 21000010 # Nothing goes to the coinbase in Clique assert paragon_chain.get_vm().state.get_balance( vm.get_block().header.coinbase) == 0
def tx(chain, funded_address, funded_address_private_key): recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c') amount = 100 vm = chain.get_vm() from_ = funded_address return new_transaction(vm, from_, recipient, amount, funded_address_private_key)
def test_state_does_not_revert_on_reserved_0xEF_byte_for_create_transaction_pre_london( pre_london_miner, funded_address, funded_address_private_key, data): chain = pre_london_miner vm = chain.get_vm() initial_balance = vm.state.get_balance(funded_address) # test the parametrized negative cases create_contract_txn_0xef_byte = new_transaction( vm=vm, from_=funded_address, to=Address(b''), amount=0, private_key=funded_address_private_key, gas=60000, nonce=0, data=data, ) block_import, _, computations = chain.mine_all( [create_contract_txn_0xef_byte], gas_limit=99904) computation = computations[0] mined_header = block_import.imported_block.header txn = block_import.imported_block.transactions[0] end_balance = computation.state.get_balance(funded_address) assert computation.is_success assert computation.state.get_nonce(funded_address) == 1 # txn consumes gas and fees assert end_balance == initial_balance - (txn.gas_price * mined_header.gas_used)
def next_transaction(self, chain: MiningChain) -> None: if self.config.to_address is None: to_address = generate_random_address() else: to_address = self.config.to_address tx = new_transaction( vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=to_address, amount=100, data=b'', nonce=self._next_nonce, ) logging.debug(f'Built Transaction {tx}') self._next_nonce = tx.nonce + 1 def callback(receipt, computation) -> None: logging.debug(f'Receipt {receipt}') logging.debug(f'Computation {computation}') return tx, callback
def mk_estimation_txn(chain, from_, from_key, data): vm = chain.get_vm() tx_params = dict(from_=from_, to=ADDR_1010, amount=200, private_key=from_key, gas=chain.header.gas_limit, data=data) return new_transaction(vm, **tx_params)
def test_missing_state_root(chain_without_block_validation, funded_address): valid_vm = chain_without_block_validation.get_vm() tx = new_transaction(valid_vm, from_=funded_address, to=ADDRESS) head = chain_without_block_validation.get_canonical_head() header_with_bad_state_root = head.copy(state_root=b'X' * 32) busted_vm = chain_without_block_validation.get_vm(header_with_bad_state_root) # notice that the state root is missing by the raised MissingAccountTrieNode with pytest.raises(MissingAccountTrieNode): busted_vm.state.apply_transaction(tx)
def tx2(chain, funded_address, funded_address_private_key): recipient = b'\x88' * 20 amount = 100 vm = chain.get_vm() from_ = funded_address return new_transaction(vm, from_, recipient, amount, funded_address_private_key, nonce=1)
def test_get_transaction_result(chain, simple_contract_address, signature, gas_price, expected): function_selector = function_signature_to_4byte_selector(signature) call_txn = new_transaction( chain.get_vm(), b'\xff' * 20, simple_contract_address, gas_price=gas_price, data=function_selector, ) result_bytes = chain.get_transaction_result(call_txn, chain.get_canonical_head()) assert result_bytes == expected
def test_get_transaction_result_revert(vm, chain_from_vm, simple_contract_address, signature, expected): chain = chain_from_vm(vm) function_selector = function_signature_to_4byte_selector(signature) call_txn = new_transaction( chain.get_vm(), b'\xff' * 20, simple_contract_address, data=function_selector, ) with pytest.raises(expected): chain.get_transaction_result(call_txn, chain.get_canonical_head())
def sstore_uint64_revert(self, chain: MiningChain) -> None: w3_tx4 = self.dos_contract.functions.storageEntropyRevert( ).buildTransaction(W3_TX_DEFAULTS) tx4 = new_transaction( vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=self.deployed_contract_address, amount=0, gas=FORTH_TX_GAS_LIMIT, data=decode_hex(w3_tx4['data']), ) block, receipt, computation = chain.apply_transaction(tx4)
def create_empty_contract_revert(self, chain: MiningChain) -> None: w3_tx5 = self.dos_contract.functions.createEmptyContractRevert( ).buildTransaction(W3_TX_DEFAULTS) tx5 = new_transaction( vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=self.deployed_contract_address, amount=0, gas=FIFTH_TX_GAS_LIMIT, data=decode_hex(w3_tx5['data']), ) block, receipt, computation = chain.apply_transaction(tx5)
def test_estimate_gas( chain_without_block_validation_from_vm, data, gas_estimator, to, on_pending, vm_cls, expected, funded_address, funded_address_private_key, should_sign_tx, ): chain = chain_without_block_validation_from_vm(vm_cls) if gas_estimator: chain.gas_estimator = gas_estimator vm = chain.get_vm() amount = 100 from_ = funded_address tx_params = dict(vm=vm, from_=from_, to=to, amount=amount, data=data) # either make a signed or unsigned transaction if should_sign_tx: tx = new_transaction(private_key=funded_address_private_key, **tx_params) else: tx = new_transaction(**tx_params) if on_pending: # estimate on *pending* block pending_header = chain.create_header_from_parent( chain.get_canonical_head()) assert chain.estimate_gas(tx, pending_header) == expected else: # estimates on top of *latest* block assert chain.estimate_gas(tx) == expected
def mine_blocks_with_receipts(chain, num_blocks, num_tx_per_block, funded_address, funded_address_private_key): for _ in range(num_blocks): block_receipts = [] for _ in range(num_tx_per_block): tx = new_transaction( chain.get_vm(), from_=funded_address, to=force_bytes_to_address(b'\x10\x10'), private_key=funded_address_private_key, ) new_block, tx_receipt, computation = chain.apply_transaction(tx) block_receipts.append(tx_receipt) computation.raise_if_error() yield chain.mine_block(), block_receipts
def test_chain_builder_mine_block_with_transactions( mining_chain, funded_address, funded_address_private_key): tx = new_transaction( mining_chain.get_vm(), from_=funded_address, to=ZERO_ADDRESS, private_key=funded_address_private_key, ) chain = build( mining_chain, mine_block(transactions=[tx]), ) block = chain.get_canonical_block_by_number(1) assert len(block.transactions) == 1 assert block.transactions[0] == tx
def test_chaindb_get_receipt_by_index( chain, funded_address, funded_address_private_key): NUMBER_BLOCKS_IN_CHAIN = 5 TRANSACTIONS_IN_BLOCK = 10 REQUIRED_BLOCK_NUMBER = 2 REQUIRED_RECEIPT_INDEX = 3 for block_number in range(NUMBER_BLOCKS_IN_CHAIN): for tx_index in range(TRANSACTIONS_IN_BLOCK): tx = new_transaction( chain.get_vm(), from_=funded_address, to=force_bytes_to_address(b'\x10\x10'), private_key=funded_address_private_key, ) new_block, tx_receipt, computation = chain.apply_transaction(tx) computation.raise_if_error() if (block_number + 1) == REQUIRED_BLOCK_NUMBER and tx_index == REQUIRED_RECEIPT_INDEX: actual_receipt = tx_receipt chain.mine_block() # Check that the receipt retrieved is indeed the actual one chaindb_retrieved_receipt = chain.chaindb.get_receipt_by_index( REQUIRED_BLOCK_NUMBER, REQUIRED_RECEIPT_INDEX, ) assert chaindb_retrieved_receipt == actual_receipt # Raise error if block number is not found with pytest.raises(ReceiptNotFound): chain.chaindb.get_receipt_by_index( NUMBER_BLOCKS_IN_CHAIN + 1, REQUIRED_RECEIPT_INDEX, ) # Raise error if receipt index is out of range with pytest.raises(ReceiptNotFound): chain.chaindb.get_receipt_by_index( NUMBER_BLOCKS_IN_CHAIN, TRANSACTIONS_IN_BLOCK + 1, )
def test_apply_transaction(chain, funded_address, funded_address_private_key, funded_address_initial_balance): vm = chain.get_vm() recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c') amount = 100 from_ = funded_address tx = new_transaction(vm, from_, recipient, amount, funded_address_private_key) receipt, computation = vm.apply_transaction(vm.get_header(), tx) new_header = vm.add_receipt_to_header(vm.get_header(), receipt) assert not computation.is_error tx_gas = tx.gas_price * constants.GAS_TX state = vm.state assert state.get_balance(from_) == (funded_address_initial_balance - amount - tx_gas) assert state.get_balance(recipient) == amount assert new_header.gas_used == constants.GAS_TX
def _erc_approve(self, addr2: str, chain: MiningChain) -> None: w3_tx = self.simple_token.functions.approve( addr2, TRANSFER_AMOUNT).buildTransaction(W3_TX_DEFAULTS) tx = new_transaction( vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=self.deployed_contract_address, amount=0, gas=SECOND_TX_GAS_LIMIT, data=decode_hex(w3_tx['data']), ) block, receipt, computation = chain.apply_transaction(tx) computation.raise_if_error() assert to_int(computation.output) == 1
def apply_transaction(self, chain: MiningChain) -> None: if self.config.to_address is None: to_address = generate_random_address() else: to_address = self.config.to_address tx = new_transaction(vm=chain.get_vm(), private_key=FUNDED_ADDRESS_PRIVATE_KEY, from_=FUNDED_ADDRESS, to=to_address, amount=100, data=b'') logging.debug(f'Applying Transaction {tx}') block, receipt, computation = chain.apply_transaction(tx) logging.debug(f'Block {block}') logging.debug(f'Receipt {receipt}') logging.debug(f'Computation {computation}')
def test_block_gap_tracking(chain, funded_address, funded_address_private_key, has_uncle, has_transaction, can_fetch_block): # Mine three common blocks common_chain = api.build( chain, api.mine_blocks(3), ) assert common_chain.get_canonical_head().block_number == 3 assert common_chain.chaindb.get_chain_gaps() == ((), 4) tx = new_transaction( common_chain.get_vm(), from_=funded_address, to=ZERO_ADDRESS, private_key=funded_address_private_key, ) uncle = api.build(common_chain, api.mine_block()).get_canonical_block_header_by_number(4) uncles = [uncle] if has_uncle else [] transactions = [tx] if has_transaction else [] # Split and have the main chain mine four blocks, the uncle chain two blocks main_chain, uncle_chain = api.build( common_chain, api.chain_split( ( # We have four different scenarios for our replaced blocks: # 1. Replaced by a trivial block without uncles or transactions # 2. Replaced by a block with transactions # 3. Replaced by a block with uncles # 4. 2 and 3 combined api.mine_block(uncles=uncles, transactions=transactions), api.mine_block(), api.mine_block(), api.mine_block(), ), # This will be the uncle chain ( api.mine_block(extra_data=b'fork-it'), api.mine_block(), ), ), ) main_head = main_chain.get_canonical_head() assert main_head.block_number == 7 assert uncle_chain.get_canonical_head().block_number == 5 assert main_chain.chaindb.get_chain_gaps() == ((), 8) assert uncle_chain.chaindb.get_chain_gaps() == ((), 6) main_header_6 = main_chain.chaindb.get_canonical_block_header_by_number(6) main_header_6_score = main_chain.chaindb.get_score(main_header_6.hash) gap_chain = api.copy(common_chain) assert gap_chain.get_canonical_head() == common_chain.get_canonical_head() gap_chain.chaindb.persist_checkpoint_header(main_header_6, main_header_6_score) # We created a gap in the chain of headers assert gap_chain.chaindb.get_header_chain_gaps() == (((4, 5), ), 7) # ...but not in the chain of blocks (yet!) assert gap_chain.chaindb.get_chain_gaps() == ((), 4) block_7 = main_chain.get_canonical_block_by_number(7) block_7_receipts = block_7.get_receipts(main_chain.chaindb) # Persist block 7 on top of the checkpoint gap_chain.chaindb.persist_unexecuted_block(block_7, block_7_receipts) assert gap_chain.chaindb.get_header_chain_gaps() == (((4, 5), ), 8) # Now we have a gap in the chain of blocks, too assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8) # Overwriting header 3 doesn't cause us to re-open a block gap gap_chain.chaindb.persist_header_chain( [main_chain.chaindb.get_canonical_block_header_by_number(3)]) assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8) # Now get the uncle block uncle_block = uncle_chain.get_canonical_block_by_number(4) uncle_block_receipts = uncle_block.get_receipts(uncle_chain.chaindb) # Put the uncle block in the gap gap_chain.chaindb.persist_unexecuted_block(uncle_block, uncle_block_receipts) assert gap_chain.chaindb.get_header_chain_gaps() == (((5, 5), ), 8) assert gap_chain.chaindb.get_chain_gaps() == (((5, 6), ), 8) # Trying to save another uncle errors as its header isn't the parent of the checkpoint second_uncle = uncle_chain.get_canonical_block_by_number(5) second_uncle_receipts = second_uncle.get_receipts(uncle_chain.chaindb) with pytest.raises(CheckpointsMustBeCanonical): gap_chain.chaindb.persist_unexecuted_block(second_uncle, second_uncle_receipts) # Now close the gap in the header chain with the actual correct headers actual_headers = [ main_chain.chaindb.get_canonical_block_header_by_number(block_number) for block_number in range(4, 7) ] gap_chain.chaindb.persist_header_chain(actual_headers) # No more gaps in the header chain assert gap_chain.chaindb.get_header_chain_gaps() == ((), 8) # We detected the de-canonicalized uncle and re-opened the block gap assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8) if can_fetch_block: # We can fetch the block even if the gap tracking reports it as missing if the block is # a "trivial" block, meaning one that doesn't have transactions nor uncles and hence # can be loaded by just the header alone. block_4 = gap_chain.get_canonical_block_by_number(4) assert block_4 == main_chain.get_canonical_block_by_number(4) else: # The uncle block was implicitly de-canonicalized with its header, # hence we can not fetch it any longer. with pytest.raises(BlockNotFound): gap_chain.get_canonical_block_by_number(4) # Add the missing block and assert the gap shrinks assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8) block_4 = main_chain.get_canonical_block_by_number(4) block_4_receipts = block_4.get_receipts(main_chain.chaindb) gap_chain.chaindb.persist_unexecuted_block(block_4, block_4_receipts) assert gap_chain.chaindb.get_chain_gaps() == (((5, 6), ), 8)