From fa3b6bc802117b2be6ee0b9b35b960f175f80c67 Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Thu, 8 Aug 2024 15:58:29 +0100 Subject: [PATCH] EVM/Kernel: rewrite delayed outbox queue resilience tezt scenario --- .../solidity_examples/spam_withdrawal.sol | 7 + etherlink/tezt/lib/cast.ml | 21 ++ etherlink/tezt/lib/cast.mli | 9 + etherlink/tezt/lib/solidity_contracts.ml | 2 +- .../evm_kernel_inputs/spam_withdrawal.abi | 29 ++- .../evm_kernel_inputs/spam_withdrawal.bin | 2 +- etherlink/tezt/tests/evm_rollup.ml | 131 ------------- etherlink/tezt/tests/evm_sequencer.ml | 179 +++++++++++++++++- 8 files changed, 238 insertions(+), 142 deletions(-) diff --git a/etherlink/kernel_evm/solidity_examples/spam_withdrawal.sol b/etherlink/kernel_evm/solidity_examples/spam_withdrawal.sol index 9acf5a2042d6..d04e0d12927d 100644 --- a/etherlink/kernel_evm/solidity_examples/spam_withdrawal.sol +++ b/etherlink/kernel_evm/solidity_examples/spam_withdrawal.sol @@ -13,4 +13,11 @@ contract SpamWithdrawals { to.call{value: 1 ether}(abi.encodeWithSignature("withdraw_base58(string)", "tz1WrbkDrzKVqcGXkjw4Qk4fXkjXpAJuNP1j")); } } + + function batchTopUp(address[] memory accounts) external { + for (uint256 i = 0; i < accounts.length; i++) { + address payable to = payable(accounts[i]); + to.transfer(5 ether); + } + } } diff --git a/etherlink/tezt/lib/cast.ml b/etherlink/tezt/lib/cast.ml index 8b6764635e5b..f2cde7dcb630 100644 --- a/etherlink/tezt/lib/cast.ml +++ b/etherlink/tezt/lib/cast.ml @@ -62,3 +62,24 @@ let craft_tx ~source_private_key ~chain_id ~nonce ~value ~gas ~gas_price tx in return (String.sub encoded_tx 2 (String.length encoded_tx - 2)) + +type wallet = {address : string; private_key : string} + +let wallet_of_json json = + let open JSON in + { + address = json |-> "address" |> as_string; + private_key = json |-> "private_key" |> as_string; + } + +let gen_wallets ~number () = + let* output = + spawn_command_and_read_string + ["wallet"; "new"; "--json"; "--number"; string_of_int number] + in + let wallets = + JSON.parse ~origin:"cast" output |> JSON.as_list_opt |> function + | Some l -> List.map wallet_of_json l + | None -> [] + in + return wallets diff --git a/etherlink/tezt/lib/cast.mli b/etherlink/tezt/lib/cast.mli index 0cecc4962179..e359db4439bf 100644 --- a/etherlink/tezt/lib/cast.mli +++ b/etherlink/tezt/lib/cast.mli @@ -46,3 +46,12 @@ val craft_tx : ?arguments:string list -> unit -> string Lwt.t + +(** Wallet is a struct containing private key and Ethereum address + derived from it (NOTE that public key is not included). *) +type wallet = {address : string; private_key : string} + +(** [gen_wallets ~number ()] + generates [number] random wallets and returns a list of [wallet] structs. + This call does not store anything in the file system. *) +val gen_wallets : number:int -> unit -> wallet list Lwt.t diff --git a/etherlink/tezt/lib/solidity_contracts.ml b/etherlink/tezt/lib/solidity_contracts.ml index b93d97d9563f..c922fdc1665d 100644 --- a/etherlink/tezt/lib/solidity_contracts.ml +++ b/etherlink/tezt/lib/solidity_contracts.ml @@ -245,7 +245,7 @@ let error () = ~contract:"Error" ~evm_version:"london" -(** The info for the "block_hash_gen.sol" contract. *) +(** The info for the "spam_withdrawal.sol" contract. *) let spam_withdrawal () = compile_contract ~source:(solidity_contracts_path ^ "/spam_withdrawal.sol") diff --git a/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.abi b/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.abi index be424c8b9646..00896c092239 100644 --- a/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.abi +++ b/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.abi @@ -1,22 +1,35 @@ [ { + "type": "function", + "name": "batchTopUp", "inputs": [ { - "internalType": "uint256", - "name": "n", - "type": "uint256" + "name": "accounts", + "type": "address[]", + "internalType": "address[]" } ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", "name": "doWithdrawals", + "inputs": [ + { + "name": "n", + "type": "uint256", + "internalType": "uint256" + } + ], "outputs": [], - "stateMutability": "payable", - "type": "function" + "stateMutability": "payable" }, { - "inputs": [], + "type": "function", "name": "giveFunds", + "inputs": [], "outputs": [], - "stateMutability": "payable", - "type": "function" + "stateMutability": "payable" } ] \ No newline at end of file diff --git a/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.bin b/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.bin index ac90ad3d98a3..18c8e8a1a326 100644 --- a/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.bin +++ b/etherlink/tezt/tests/evm_kernel_inputs/spam_withdrawal.bin @@ -1 +1 @@ -608060405234801561000f575f80fd5b5061033b8061001d5f395ff3fe608060405260043610610028575f3560e01c8063ce948b401461002c578063e57d95fa14610036575b5f80fd5b610034610052565b005b610050600480360381019061004b91906101ba565b610054565b005b565b5f5b8181101561017f575f73ff0000000000000000000000000000000000000190508073ffffffffffffffffffffffffffffffffffffffff16670de0b6b3a76400006040516024016100a590610265565b6040516020818303038152906040527fcda4fee2000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161012f91906102ef565b5f6040518083038185875af1925050503d805f8114610169576040519150601f19603f3d011682016040523d82523d5f602084013e61016e565b606091505b505050508080600101915050610056565b5050565b5f80fd5b5f819050919050565b61019981610187565b81146101a3575f80fd5b50565b5f813590506101b481610190565b92915050565b5f602082840312156101cf576101ce610183565b5b5f6101dc848285016101a6565b91505092915050565b5f82825260208201905092915050565b7f747a315772626b44727a4b56716347586b6a7734516b3466586b6a5870414a755f8201527f4e50316a00000000000000000000000000000000000000000000000000000000602082015250565b5f61024f6024836101e5565b915061025a826101f5565b604082019050919050565b5f6020820190508181035f83015261027c81610243565b9050919050565b5f81519050919050565b5f81905092915050565b5f5b838110156102b4578082015181840152602081019050610299565b5f8484015250505050565b5f6102c982610283565b6102d3818561028d565b93506102e3818560208601610297565b80840191505092915050565b5f6102fa82846102bf565b91508190509291505056fea2646970667358221220d9cbbe171c6ea59a82e856c8ab9bc3656658e6cb2607ff35f9bcb8a1375c5be964736f6c63430008160033 \ No newline at end of file +0x608060405234801561001057600080fd5b50610367806100206000396000f3fe6080604052600436106100345760003560e01c8063b33bcea414610039578063ce948b4014610059578063e57d95fa1461005b575b600080fd5b34801561004557600080fd5b5061005961005436600461020e565b61006e565b005b6100596100693660046102d3565b6100e7565b60005b81518110156100e357600082828151811061008e5761008e6102ec565b60200260200101519050806001600160a01b03166108fc674563918244f400009081150290604051600060405180830381858888f193505050501580156100d9573d6000803e3d6000fd5b5050600101610071565b5050565b60005b818110156100e357604051600160ff60981b01908190670de0b6b3a7640000906101559060240160208082526024908201527f747a315772626b44727a4b56716347586b6a7734516b3466586b6a5870414a75604082015263272818b560e11b606082015260800190565b60408051601f198184030181529181526020820180516001600160e01b03166366d27f7160e11b1790525161018a9190610302565b60006040518083038185875af1925050503d80600081146101c7576040519150601f19603f3d011682016040523d82523d6000602084013e6101cc565b606091505b5050600190920191506100ea9050565b634e487b7160e01b600052604160045260246000fd5b80356001600160a01b038116811461020957600080fd5b919050565b6000602080838503121561022157600080fd5b823567ffffffffffffffff8082111561023957600080fd5b818501915085601f83011261024d57600080fd5b81358181111561025f5761025f6101dc565b8060051b604051601f19603f83011681018181108582111715610284576102846101dc565b6040529182528482019250838101850191888311156102a257600080fd5b938501935b828510156102c7576102b8856101f2565b845293850193928501926102a7565b98975050505050505050565b6000602082840312156102e557600080fd5b5035919050565b634e487b7160e01b600052603260045260246000fd5b6000825160005b818110156103235760208186018101518583015201610309565b50600092019182525091905056fea2646970667358221220c24a90f7a31cbc743551c74b18840cfd9f11dbf9e35fd9c553c11e4b73dadc5a64736f6c63430008160033 \ No newline at end of file diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index beb67fb98de1..52e2ca243241 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -5706,135 +5706,6 @@ let test_block_gas_limit = check_gas_limit block_gas_limit ; unit -(** Test that the kernel can handle more than 100 withdrawals withdrawals - per level, which is currently the limit of outbox messages in the L1. *) -let test_outbox_size_limit_resilience ~slow = - let admin = Constant.bootstrap5 in - let commitment_period = 5 and challenge_window = 5 in - let slow_str = if slow then "slow" else "fast" in - register_proxy - ~tags: - (["evm"; "withdraw"; "outbox"; "spam"] @ if slow then [Tag.slow] else []) - ~title:(sf "Outbox size limit resilience (%s)" slow_str) - ~admin - ~commitment_period - ~challenge_window - @@ fun ~protocol:_ ~evm_setup -> - let { - evm_node; - sc_rollup_node; - client; - endpoint; - sc_rollup_address; - l1_contracts; - produce_block; - _; - } = - evm_setup - in - (* Deposit tickets to the rollup to perform the withdrawals. *) - let* () = - let bridge = - let l1_contracts = Option.get l1_contracts in - l1_contracts.bridge - in - Client.transfer - ~entrypoint:"deposit" - ~arg: - (sf - "Pair %S 0x1074Fd1EC02cbeaa5A90450505cF3B48D834f3EB" - sc_rollup_address) - ~amount:(Tez.of_int 1000) - ~giver:Constant.bootstrap5.public_key_hash - ~receiver:bridge - ~burn_cap:Tez.one - client - in - let*@ _ = produce_block () in - let sender = Eth_account.bootstrap_accounts.(0) in - let* spam_withdrawal_resolved = spam_withdrawal () in - (* Deploy the spam contract. *) - let* contract, _tx = - deploy ~contract:spam_withdrawal_resolved ~sender evm_setup - in - - (* Start by giving funds to the contract. This cannot be done in one go - because the stupid [eth-cli] doesn't include the transfer in gas - estimation, which makes the next call fail. *) - let* _tx_give_fund = - let give_funds () = - Eth_cli.contract_send - ~source_private_key:sender.private_key - ~endpoint - ~abi_label:spam_withdrawal_resolved.label - ~address:contract - ~method_call:"giveFunds()" - ~value:(Wei.of_eth_int 200) - in - wait_for_application ~produce_block (give_funds ()) - in - - let* withdrawal_level = Client.level client in - - (* Produce 120 withdrawals by calling the spam entrypoint. *) - let* do_withdrawals = - let do_withdrawals () = - Eth_cli.contract_send - ~source_private_key:sender.private_key - ~endpoint - ~abi_label:spam_withdrawal_resolved.label - ~address:contract - ~method_call:"doWithdrawals(120)" - ~value:(Wei.of_eth_int 200) - in - wait_for_application ~produce_block (do_withdrawals ()) - in - (* The transaction tries to do more than 100 outbox messages in a Tezos level. - If the kernel is not smart about it, it will hard fail and revert its state. - Therefore checking if the transaction is a success is a good indicator - of the correct behavior. *) - let* () = check_tx_succeeded ~endpoint ~tx:do_withdrawals in - - if slow then ( - (* Execute the first 100 withdrawals *) - let* actual_withdrawal_level = - find_and_execute_withdrawal - ~withdrawal_level - ~commitment_period - ~challenge_window - ~evm_node - ~sc_rollup_node - ~sc_rollup_address - ~client - in - let* balance = - Client.get_balance_for - ~account:"tz1WrbkDrzKVqcGXkjw4Qk4fXkjXpAJuNP1j" - client - in - Check.((balance = Tez.of_int 100) Tez.typ) - ~error_msg:"Expected balance of %R, got %L" ; - (* Execute the next 20 withdrawals *) - let* _ = - find_and_execute_withdrawal - ~withdrawal_level:(actual_withdrawal_level + 1) - ~commitment_period - ~challenge_window - ~evm_node - ~sc_rollup_node - ~sc_rollup_address - ~client - in - let* balance = - Client.get_balance_for - ~account:"tz1WrbkDrzKVqcGXkjw4Qk4fXkjXpAJuNP1j" - client - in - Check.((balance = Tez.of_int 120) Tez.typ) - ~error_msg:"Expected balance of %R, got %L" ; - unit) - else unit - let test_tx_pool_timeout = Protocol.register_test ~__FILE__ @@ -6520,8 +6391,6 @@ let register_evm_node ~protocols = test_block_constants_opcode protocols ; test_revert_is_correctly_propagated protocols ; test_block_gas_limit protocols ; - test_outbox_size_limit_resilience ~slow:true protocols ; - test_outbox_size_limit_resilience ~slow:false protocols ; test_tx_pool_timeout protocols ; test_tx_pool_address_boundaries protocols ; test_tx_pool_transaction_size_exceeded protocols ; diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 19104246bcb0..616a25231ee6 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -4200,6 +4200,181 @@ let test_sequencer_dont_read_level_twice = unit +(** Test that the kernel can handle more than 100 withdrawals per level, + which is currently the limit of outbox messages in the L1. *) +let test_outbox_size_limit_resilience ~slow = + let commitment_period = 5 and challenge_window = 5 in + let slow_str = if slow then "slow" else "fast" in + register_all + ~sequencer:Constant.bootstrap1 + ~time_between_blocks:Nothing + ~tags: + (["evm"; "withdraw"; "outbox"; "spam"] @ if slow then [Tag.slow] else []) + ~title:(sf "Outbox size limit resilience (%s)" slow_str) + ~commitment_period + ~challenge_window + @@ fun { + client; + l1_contracts; + sc_rollup_address; + sc_rollup_node; + sequencer; + proxy; + _; + } + _protocol -> + let endpoint = Evm_node.endpoint sequencer in + + (* Make a tez deposit *) + let amount = Tez.of_int 1000 in + let depositor = Constant.bootstrap5 in + let receiver = + Eth_account. + { + address = "0x1074Fd1EC02cbeaa5A90450505cF3B48D834f3EB"; + private_key = + "0xb7c548b5442f5b28236f0dcd619f65aaaafd952240908adcf9642d8e616587ee"; + } + in + let* receiver_balance_prev = + Eth_cli.balance ~account:receiver.address ~endpoint + in + let* () = + send_deposit_to_delayed_inbox + ~amount + ~l1_contracts + ~depositor + ~receiver:receiver.address + ~sc_rollup_node + ~sc_rollup_address + client + in + let* () = + wait_for_delayed_inbox_add_tx_and_injected + ~sequencer + ~sc_rollup_node + ~client + in + let* () = bake_until_sync ~sc_rollup_node ~proxy ~sequencer ~client () in + let* () = check_delayed_inbox_is_empty ~sc_rollup_node in + let* receiver_balance_next = + Eth_cli.balance ~account:receiver.address ~endpoint + in + Check.((receiver_balance_next > receiver_balance_prev) Wei.typ) + ~error_msg:"Expected a bigger balance" ; + + (* Deploy and top up batch withdrawal contract *) + let* spammer_resolved = Solidity_contracts.spam_withdrawal () in + let* () = + Eth_cli.add_abi ~label:spammer_resolved.label ~abi:spammer_resolved.abi () + in + let* spammer_contract, _tx_hash = + send_transaction_to_sequencer + (fun () -> + Eth_cli.deploy + ~source_private_key:receiver.private_key + ~endpoint:(Evm_node.endpoint sequencer) + ~abi:spammer_resolved.abi + ~bin:spammer_resolved.bin) + sequencer + in + let* _ = + send_transaction_to_sequencer + (Eth_cli.contract_send + ~source_private_key:receiver.private_key + ~endpoint + ~abi_label:spammer_resolved.label + ~address:spammer_contract + ~method_call:"giveFunds()" + ~value:(Wei.of_eth_int 990)) + sequencer + in + + (* Create and topup 30 accounts (to overcome single account per block limitation) *) + let* wallets = Cast.gen_wallets ~number:30 () in + let addresses = + List.map (fun (w : Cast.wallet) -> sf "\"%s\"" w.address) wallets + in + let* _ = + send_transaction_to_sequencer + (Eth_cli.contract_send + ~source_private_key:receiver.private_key + ~endpoint + ~abi_label:spammer_resolved.label + ~address:spammer_contract + ~method_call:(sf "batchTopUp([%s])" (String.concat "," addresses)) + ~value:(Wei.of_eth_int 0)) + sequencer + in + + (* Create 150 withdrawals *) + let* withdrawal_level = Client.level client in + let withdraw_tx ~(wallet : Cast.wallet) = + Cast.craft_tx + ~source_private_key:wallet.private_key + ~chain_id:1337 + ~nonce:0 + ~gas_price:1_000_000_000 + ~gas:30_000_000 + ~value:(Wei.of_eth_int 0) + ~address:spammer_contract + ~signature:"doWithdrawals(uint256)" + ~arguments:["5"] + in + let* withdraw_txs = + List.map (fun w -> withdraw_tx ~wallet:w ()) wallets |> Lwt.all + in + let* _ = batch_n_transactions ~evm_node:sequencer withdraw_txs in + let* _ = produce_block sequencer in + + (* At this point the outbox queue must contain 150 messages *) + let* () = bake_until_sync ~sc_rollup_node ~client ~sequencer ~proxy () in + + (* 100 messages flushed at this point *) + let* _ = produce_block sequencer in + let* () = bake_until_sync ~sc_rollup_node ~client ~sequencer ~proxy () in + + (* +50 messages flushed at this point *) + if slow then ( + (* Execute the first 100 withdrawals *) + let* actual_withdrawal_level = + find_and_execute_withdrawal + ~withdrawal_level + ~commitment_period + ~challenge_window + ~evm_node:proxy + ~sc_rollup_node + ~sc_rollup_address + ~client + in + let* balance = + Client.get_balance_for + ~account:"tz1WrbkDrzKVqcGXkjw4Qk4fXkjXpAJuNP1j" + client + in + Check.((balance = Tez.of_int 100) Tez.typ) + ~error_msg:"Expected balance of %R, got %L" ; + (* Execute the next 50 withdrawals *) + let* _ = + find_and_execute_withdrawal + ~withdrawal_level:(actual_withdrawal_level + 1) + ~commitment_period + ~challenge_window + ~evm_node:proxy + ~sc_rollup_node + ~sc_rollup_address + ~client + in + let* balance = + Client.get_balance_for + ~account:"tz1WrbkDrzKVqcGXkjw4Qk4fXkjXpAJuNP1j" + client + in + Check.((balance = Tez.of_int 150) Tez.typ) + ~error_msg:"Expected balance of %R, got %L" ; + unit) + else unit + let test_stage_one_reboot = register_all ~sequencer:Constant.bootstrap1 @@ -6202,4 +6377,6 @@ let () = test_trace_transaction_call_trace_certain_depth protocols ; test_trace_transaction_call_trace_revert protocols ; test_trace_transaction_calltracer_multiple_txs protocols ; - test_debug_print_store_schemas () + test_debug_print_store_schemas () ; + test_outbox_size_limit_resilience ~slow:true protocols ; + test_outbox_size_limit_resilience ~slow:false protocols -- GitLab