From 62115da72824a3f6489761edec60edc6d1bbca32 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Sun, 7 Jul 2024 21:52:15 -0400 Subject: [PATCH] pull in changes, this is broken --- .dockerignore | 11 + .github/workflows/arbitrator-ci.yml | 52 +- .github/workflows/ci.yml | 47 +- .github/workflows/codeql-analysis.yml | 5 +- .github/workflows/merge-checks.yml | 20 + .gitignore | 1 + .gitmodules | 14 +- .golangci.yml | 7 +- Dockerfile | 108 +- Makefile | 315 ++- README.md | 2 +- arbcompress/compress_cgo.go | 55 - arbcompress/compress_common.go | 11 +- arbcompress/compress_wasm.go | 34 - arbcompress/native.go | 76 + arbcompress/raw.s | 16 - arbcompress/wasm.go | 64 + arbitrator/Cargo.lock | 1540 ++++++------ arbitrator/Cargo.toml | 26 + arbitrator/arbutil/Cargo.toml | 11 +- arbitrator/arbutil/src/color.rs | 5 + arbitrator/arbutil/src/crypto.rs | 25 + arbitrator/arbutil/src/evm/api.rs | 192 ++ arbitrator/arbutil/src/evm/mod.rs | 101 + arbitrator/arbutil/src/evm/req.rs | 301 +++ arbitrator/arbutil/src/evm/storage.rs | 73 + arbitrator/arbutil/src/evm/user.rs | 91 + arbitrator/arbutil/src/format.rs | 54 +- arbitrator/arbutil/src/lib.rs | 47 +- arbitrator/arbutil/src/math.rs | 43 + arbitrator/arbutil/src/operator.rs | 1209 +++++++++ arbitrator/arbutil/src/pricing.rs | 20 + arbitrator/arbutil/src/types.rs | 235 ++ arbitrator/brotli/Cargo.toml | 21 + arbitrator/brotli/build.rs | 18 + arbitrator/brotli/fuzz/.gitignore | 4 + arbitrator/brotli/fuzz/Cargo.toml | 36 + arbitrator/brotli/fuzz/README | 13 + .../brotli/fuzz/fuzz_targets/compress.rs | 18 + .../brotli/fuzz/fuzz_targets/decompress.rs | 28 + .../brotli/fuzz/fuzz_targets/round_trip.rs | 23 + arbitrator/brotli/src/cgo.rs | 66 + arbitrator/brotli/src/dicts/mod.rs | 97 + .../brotli/src/dicts/stylus-program-11.lz | Bin 0 -> 112640 bytes arbitrator/brotli/src/lib.rs | 310 +++ arbitrator/brotli/src/types.rs | 102 + arbitrator/brotli/src/wasmer_traits.rs | 29 + arbitrator/caller-env/Cargo.toml | 17 + arbitrator/caller-env/src/brotli/mod.rs | 72 + arbitrator/caller-env/src/guest_ptr.rs | 49 + arbitrator/caller-env/src/lib.rs | 67 + arbitrator/caller-env/src/static_caller.rs | 119 + arbitrator/caller-env/src/wasip1_stub.rs | 407 +++ arbitrator/caller-env/src/wasmer_traits.rs | 35 + arbitrator/cbindgen.toml | 1 - arbitrator/jit/Cargo.toml | 12 +- arbitrator/jit/build.rs | 7 - arbitrator/jit/src/arbcompress.rs | 122 +- arbitrator/jit/src/caller_env.rs | 185 ++ arbitrator/jit/src/color.rs | 89 - arbitrator/jit/src/gostack.rs | 216 -- arbitrator/jit/src/machine.rs | 186 +- arbitrator/jit/src/main.rs | 58 +- arbitrator/jit/src/program.rs | 265 ++ arbitrator/jit/src/runtime.rs | 104 - arbitrator/jit/src/socket.rs | 21 +- arbitrator/jit/src/stylus_backend.rs | 188 ++ arbitrator/jit/src/wasip1_stub.rs | 162 ++ arbitrator/jit/src/wavmio.rs | 275 +- arbitrator/langs/bf | 1 + arbitrator/langs/c | 1 + arbitrator/langs/rust | 1 + arbitrator/prover/Cargo.toml | 32 +- arbitrator/prover/src/binary.rs | 493 +++- arbitrator/prover/src/bulk_memory.wat | 2 +- arbitrator/prover/src/host.rs | 269 +- arbitrator/prover/src/kzg.rs | 2 +- arbitrator/prover/src/kzgbn254.rs | 2 +- arbitrator/prover/src/lib.rs | 171 +- arbitrator/prover/src/machine.rs | 2101 +++++++++++----- arbitrator/prover/src/main.rs | 63 +- arbitrator/prover/src/memory.rs | 75 +- arbitrator/prover/src/merkle.rs | 59 +- arbitrator/prover/src/print.rs | 303 +++ arbitrator/prover/src/programs/config.rs | 222 ++ arbitrator/prover/src/programs/counter.rs | 155 ++ arbitrator/prover/src/programs/depth.rs | 541 ++++ arbitrator/prover/src/programs/dynamic.rs | 154 ++ arbitrator/prover/src/programs/heap.rs | 117 + arbitrator/prover/src/programs/memory.rs | 125 + arbitrator/prover/src/programs/meter.rs | 532 ++++ arbitrator/prover/src/programs/mod.rs | 479 ++++ arbitrator/prover/src/programs/prelude.rs | 12 + arbitrator/prover/src/programs/start.rs | 67 + arbitrator/prover/src/test.rs | 71 + arbitrator/prover/src/utils.rs | 145 +- arbitrator/prover/src/value.rs | 218 +- arbitrator/prover/src/wavm.rs | 151 +- arbitrator/prover/test-cases/block.wat | 5 + arbitrator/prover/test-cases/bulk-memory.wat | 7 +- .../prover/test-cases/call-indirect.wat | 5 + arbitrator/prover/test-cases/call.wat | 5 + arbitrator/prover/test-cases/const.wat | 5 + arbitrator/prover/test-cases/div-overflow.wat | 5 + arbitrator/prover/test-cases/dynamic.wat | 91 + arbitrator/prover/test-cases/forward-test.wat | 32 + .../prover/test-cases/forward/forward.wat | 8 + .../prover/test-cases/forward/target.wat | 27 + .../prover/test-cases/global-state-wavm.wat | 23 + .../test-cases/global-state-wrapper.wat | 8 +- arbitrator/prover/test-cases/global-state.wat | 6 +- arbitrator/prover/test-cases/globals.wat | 8 +- arbitrator/prover/test-cases/go/main.go | 121 +- arbitrator/prover/test-cases/if-else.wat | 5 + arbitrator/prover/test-cases/iops.wat | 7 +- arbitrator/prover/test-cases/link.txt | 13 + arbitrator/prover/test-cases/link.wat | 89 + arbitrator/prover/test-cases/locals.wat | 6 +- arbitrator/prover/test-cases/loop.wat | 7 +- arbitrator/prover/test-cases/math.wat | 5 + .../prover/test-cases/read-inboxmsg-10.wat | 8 +- arbitrator/prover/test-cases/return.wat | 6 +- arbitrator/prover/test-cases/user.wat | 53 + arbitrator/stylus/Cargo.toml | 42 + arbitrator/stylus/cbindgen.toml | 13 + arbitrator/stylus/src/benchmarks.rs | 92 + arbitrator/stylus/src/cache.rs | 159 ++ arbitrator/stylus/src/env.rs | 259 ++ arbitrator/stylus/src/evm_api.rs | 50 + arbitrator/stylus/src/host.rs | 466 ++++ arbitrator/stylus/src/lib.rs | 276 ++ arbitrator/stylus/src/native.rs | 454 ++++ arbitrator/stylus/src/run.rs | 123 + arbitrator/stylus/src/test/api.rs | 210 ++ arbitrator/stylus/src/test/misc.rs | 82 + arbitrator/stylus/src/test/mod.rs | 196 ++ arbitrator/stylus/src/test/native.rs | 499 ++++ arbitrator/stylus/src/test/sdk.rs | 65 + arbitrator/stylus/src/test/timings.rs | 73 + arbitrator/stylus/src/test/wavm.rs | 104 + arbitrator/stylus/src/util.rs | 20 + arbitrator/stylus/tests/.cargo/config.toml | 9 + arbitrator/stylus/tests/add.wat | 14 + .../stylus/tests/bad-mods/bad-export.wat | 5 + .../stylus/tests/bad-mods/bad-export2.wat | 5 + .../stylus/tests/bad-mods/bad-export3.wat | 5 + .../stylus/tests/bad-mods/bad-export4.wat | 7 + .../stylus/tests/bad-mods/bad-import.wat | 5 + arbitrator/stylus/tests/bf/.gitignore | 2 + arbitrator/stylus/tests/bf/cat.b | 1 + arbitrator/stylus/tests/bulk-memory-oob.wat | 17 + arbitrator/stylus/tests/clz.wat | 11 + arbitrator/stylus/tests/console.wat | 37 + arbitrator/stylus/tests/create/.cargo/config | 2 + arbitrator/stylus/tests/create/Cargo.lock | 544 ++++ arbitrator/stylus/tests/create/Cargo.toml | 19 + arbitrator/stylus/tests/create/src/main.rs | 26 + arbitrator/stylus/tests/depth.wat | 18 + arbitrator/stylus/tests/erc20/Cargo.lock | 890 +++++++ arbitrator/stylus/tests/erc20/Cargo.toml | 23 + arbitrator/stylus/tests/erc20/src/erc20.rs | 178 ++ arbitrator/stylus/tests/erc20/src/main.rs | 70 + .../stylus/tests/evm-data/.cargo/config | 2 + arbitrator/stylus/tests/evm-data/Cargo.lock | 544 ++++ arbitrator/stylus/tests/evm-data/Cargo.toml | 19 + arbitrator/stylus/tests/evm-data/src/main.rs | 85 + .../stylus/tests/exit-early/exit-early.wat | 24 + .../tests/exit-early/panic-after-write.wat | 19 + .../stylus/tests/fallible/.cargo/config | 2 + arbitrator/stylus/tests/fallible/Cargo.lock | 543 ++++ arbitrator/stylus/tests/fallible/Cargo.toml | 18 + arbitrator/stylus/tests/fallible/src/main.rs | 16 + arbitrator/stylus/tests/grow/fixed.wat | 25 + arbitrator/stylus/tests/grow/grow-120.wat | 9 + .../stylus/tests/grow/grow-and-call.wat | 37 + arbitrator/stylus/tests/grow/mem-write.wat | 45 + arbitrator/stylus/tests/keccak-100/Cargo.lock | 544 ++++ arbitrator/stylus/tests/keccak-100/Cargo.toml | 19 + .../stylus/tests/keccak-100/src/main.rs | 23 + arbitrator/stylus/tests/keccak/Cargo.lock | 544 ++++ arbitrator/stylus/tests/keccak/Cargo.toml | 19 + arbitrator/stylus/tests/keccak/src/main.rs | 26 + arbitrator/stylus/tests/log/.cargo/config | 2 + arbitrator/stylus/tests/log/Cargo.lock | 544 ++++ arbitrator/stylus/tests/log/Cargo.toml | 19 + arbitrator/stylus/tests/log/src/main.rs | 20 + arbitrator/stylus/tests/math/Cargo.lock | 543 ++++ arbitrator/stylus/tests/math/Cargo.toml | 18 + arbitrator/stylus/tests/math/src/main.rs | 39 + arbitrator/stylus/tests/memory.wat | 59 + arbitrator/stylus/tests/memory2.wat | 12 + arbitrator/stylus/tests/module-mod.wat | 9 + .../stylus/tests/multicall/.cargo/config | 2 + arbitrator/stylus/tests/multicall/Cargo.lock | 838 +++++++ arbitrator/stylus/tests/multicall/Cargo.toml | 23 + arbitrator/stylus/tests/multicall/src/main.rs | 121 + .../tests/read-return-data/.cargo/config | 3 + .../stylus/tests/read-return-data/Cargo.lock | 544 ++++ .../stylus/tests/read-return-data/Cargo.toml | 20 + .../stylus/tests/read-return-data/src/main.rs | 65 + .../stylus/tests/sdk-storage/.cargo/config | 2 + .../stylus/tests/sdk-storage/Cargo.lock | 600 +++++ .../stylus/tests/sdk-storage/Cargo.toml | 19 + .../stylus/tests/sdk-storage/src/main.rs | 323 +++ arbitrator/stylus/tests/start.wat | 18 + arbitrator/stylus/tests/storage/.cargo/config | 2 + arbitrator/stylus/tests/storage/Cargo.lock | 543 ++++ arbitrator/stylus/tests/storage/Cargo.toml | 15 + arbitrator/stylus/tests/storage/src/main.rs | 48 + arbitrator/stylus/tests/test.wat | 5 + arbitrator/stylus/tests/timings/Cargo.lock | 746 ++++++ .../stylus/tests/timings/block_basefee.wat | 36 + .../stylus/tests/timings/block_coinbase.wat | 36 + .../stylus/tests/timings/block_gas_limit.wat | 36 + .../stylus/tests/timings/block_number.wat | 36 + .../stylus/tests/timings/block_timestamp.wat | 36 + arbitrator/stylus/tests/timings/chainid.wat | 36 + .../stylus/tests/timings/contract_address.wat | 36 + .../stylus/tests/timings/evm_gas_left.wat | 36 + .../stylus/tests/timings/evm_ink_left.wat | 36 + arbitrator/stylus/tests/timings/keccak.wat | 35 + .../stylus/tests/timings/msg_sender.wat | 36 + arbitrator/stylus/tests/timings/msg_value.wat | 36 + arbitrator/stylus/tests/timings/null_host.wat | 35 + arbitrator/stylus/tests/timings/read_args.wat | 34 + .../stylus/tests/timings/return_data_size.wat | 36 + .../stylus/tests/timings/tx_gas_price.wat | 36 + .../stylus/tests/timings/tx_ink_price.wat | 36 + arbitrator/stylus/tests/timings/tx_origin.wat | 36 + .../stylus/tests/timings/write_result.wat | 34 + arbitrator/tools/module_roots/Cargo.lock | 2232 +++++++++++++++++ arbitrator/tools/module_roots/Cargo.toml | 12 + arbitrator/tools/module_roots/src/main.rs | 76 + arbitrator/tools/wasmer | 1 + arbitrator/wasm-libraries/Cargo.lock | 2193 ++++++++++++++-- arbitrator/wasm-libraries/Cargo.toml | 10 +- .../wasm-libraries/arbcompress/Cargo.toml | 13 + .../{brotli => arbcompress}/build.rs | 4 + .../wasm-libraries/arbcompress/src/lib.rs | 45 + arbitrator/wasm-libraries/brotli/src/lib.rs | 84 - arbitrator/wasm-libraries/forward/.gitignore | 1 + arbitrator/wasm-libraries/forward/Cargo.toml | 8 + arbitrator/wasm-libraries/forward/src/main.rs | 206 ++ arbitrator/wasm-libraries/go-abi/Cargo.toml | 7 - arbitrator/wasm-libraries/go-abi/src/lib.rs | 92 - arbitrator/wasm-libraries/go-stub/Cargo.toml | 16 - arbitrator/wasm-libraries/go-stub/src/lib.rs | 598 ----- arbitrator/wasm-libraries/host-io/Cargo.toml | 4 +- arbitrator/wasm-libraries/host-io/src/lib.rs | 191 +- .../{brotli => program-exec}/Cargo.toml | 4 +- .../wasm-libraries/program-exec/src/lib.rs | 58 + .../wasm-libraries/user-host-trait/Cargo.toml | 11 + .../wasm-libraries/user-host-trait/src/lib.rs | 952 +++++++ .../wasm-libraries/user-host/Cargo.toml | 17 + .../wasm-libraries/user-host/src/host.rs | 289 +++ .../wasm-libraries/user-host/src/ink.rs | 38 + .../wasm-libraries/user-host/src/lib.rs | 7 + .../wasm-libraries/user-host/src/link.rs | 280 +++ .../wasm-libraries/user-host/src/program.rs | 273 ++ .../wasm-libraries/user-test/Cargo.toml | 18 + .../wasm-libraries/user-test/src/host.rs | 238 ++ .../wasm-libraries/user-test/src/ink.rs | 38 + .../wasm-libraries/user-test/src/lib.rs | 55 + .../wasm-libraries/user-test/src/program.rs | 220 ++ .../wasm-libraries/wasi-stub/Cargo.toml | 3 + .../wasm-libraries/wasi-stub/src/lib.rs | 237 +- arbitrator/wasm-testsuite/Cargo.lock | 1033 +++++++- arbitrator/wasm-testsuite/Cargo.toml | 1 + arbitrator/wasm-testsuite/check.sh | 2 +- arbitrator/wasm-testsuite/src/main.rs | 186 +- arbnode/api.go | 9 +- arbnode/batch_poster.go | 192 +- arbnode/dataposter/data_poster.go | 142 +- arbnode/dataposter/dataposter_test.go | 22 +- .../externalsigner/externalsigner.go | 115 - .../externalsigner/externalsigner_test.go | 74 - .../externalsignertest/externalsignertest.go | 61 +- arbnode/dataposter/storage_test.go | 3 +- arbnode/inbox_reader.go | 29 +- arbnode/inbox_test.go | 5 +- arbnode/inbox_tracker.go | 172 +- arbnode/message_pruner.go | 25 +- arbnode/message_pruner_test.go | 6 +- arbnode/node.go | 148 +- arbnode/schema.go | 1 + arbnode/sequencer_inbox.go | 4 +- arbnode/sync_monitor.go | 230 +- arbnode/transaction_streamer.go | 243 +- arbos/activate_test.go | 106 + arbos/arbosState/arbosstate.go | 122 +- arbos/arbosState/initialization_test.go | 2 +- arbos/arbosState/initialize.go | 7 +- arbos/arbostypes/incomingmessage.go | 2 - arbos/arbostypes/messagewithmeta.go | 5 + arbos/block_processor.go | 25 +- arbos/burn/burn.go | 10 + arbos/internal_tx.go | 6 +- arbos/l1pricing/l1PricingOldVersions.go | 21 +- arbos/l1pricing/l1pricing.go | 17 + arbos/l1pricing_test.go | 11 +- arbos/l2pricing/model.go | 6 +- arbos/programs/api.go | 427 ++++ arbos/programs/constant_test.go | 13 + arbos/programs/data_pricer.go | 90 + arbos/programs/memory.go | 60 + arbos/programs/memory_test.go | 87 + arbos/programs/native.go | 342 +++ arbos/programs/native_api.go | 95 + arbos/programs/params.go | 159 ++ arbos/programs/programs.go | 553 ++++ arbos/programs/testconstants.go | 101 + arbos/programs/wasm.go | 200 ++ arbos/programs/wasm_api.go | 63 + arbos/programs/wasmstorehelper.go | 80 + arbos/retryables/retryable.go | 2 +- arbos/storage/storage.go | 116 +- arbos/tx_processor.go | 111 +- arbos/util/tracing.go | 4 +- arbos/util/transfer.go | 13 +- arbos/util/util.go | 110 +- arbstate/daprovider/reader.go | 104 + .../{das_reader.go => daprovider/util.go} | 170 +- arbstate/daprovider/writer.go | 47 + arbstate/inbox.go | 300 +-- arbstate/inbox_fuzz_test.go | 3 +- arbutil/correspondingl1blocknumber.go | 2 +- arbutil/format.go | 19 + arbutil/unsafe.go | 28 + arbutil/wait_for_l1.go | 3 +- blocks_reexecutor/blocks_reexecutor.go | 90 +- broadcastclient/broadcastclient_test.go | 12 +- broadcaster/broadcaster.go | 25 +- broadcaster/broadcaster_test.go | 14 +- broadcaster/message/message.go | 1 + .../message/message_serialization_test.go | 35 +- cmd/chaininfo/arbitrum_chain_info.json | 49 + cmd/conf/chain.go | 18 +- cmd/conf/database.go | 188 +- cmd/conf/init.go | 44 +- cmd/daserver/daserver.go | 37 +- .../data_availability_check.go | 8 +- cmd/datool/datool.go | 39 +- cmd/deploy/deploy.go | 6 +- cmd/genericconf/config.go | 8 +- cmd/genericconf/filehandler_test.go | 7 +- cmd/genericconf/logging.go | 106 +- cmd/genericconf/loglevel.go | 38 + cmd/genericconf/server.go | 2 + .../{ipfshelper.go => ipfshelper.bkup_go} | 3 + cmd/ipfshelper/ipfshelper_stub.go | 31 + cmd/ipfshelper/ipfshelper_test.go | 3 + cmd/nitro-val/config.go | 8 +- cmd/nitro-val/nitro_val.go | 6 +- cmd/nitro/config_test.go | 28 +- cmd/nitro/init.go | 273 +- cmd/nitro/init_test.go | 179 ++ cmd/nitro/nitro.go | 142 +- cmd/pruning/pruning.go | 14 +- cmd/relay/relay.go | 15 +- cmd/replay/main.go | 35 +- cmd/staterecovery/staterecovery.go | 2 +- cmd/util/confighelpers/configuration.go | 43 +- contracts | 1 - das/aggregator.go | 60 +- das/aggregator_test.go | 20 +- das/cache_storage_service.go | 4 +- das/chain_fetch_das.go | 20 +- das/das.go | 39 +- das/dasRpcClient.go | 131 +- das/dasRpcServer.go | 250 +- das/das_test.go | 16 +- das/dastree/dastree.go | 7 +- das/dastree/dastree_test.go | 3 +- das/db_storage_service.go | 33 +- das/extra_signature_checker_test.go | 36 +- das/factory.go | 145 +- das/fallback_storage_service.go | 6 +- das/ipfs_storage_service.go | 252 -- das/ipfs_storage_service_test.go | 57 - das/iterable_storage_service.go | 147 -- das/local_file_storage_service.go | 40 +- das/memory_backed_storage_service.go | 16 +- das/panic_wrapper.go | 6 +- das/read_limited.go | 16 +- das/reader_aggregator_strategies.go | 28 +- das/reader_aggregator_strategies_test.go | 12 +- das/redis_storage_service.go | 27 +- das/redundant_storage_service.go | 20 +- das/regular_sync_storage_test.go | 79 - das/regularly_sync_storage.go | 95 - das/restful_client.go | 10 +- das/restful_server.go | 8 +- das/rpc_aggregator.go | 21 +- das/rpc_test.go | 93 +- das/s3_storage_service.go | 38 +- das/sign_after_store_das_writer.go | 115 +- das/signature_verifier.go | 126 + das/simple_das_reader_aggregator.go | 22 +- das/storage_service.go | 4 +- das/store_signing.go | 54 +- das/store_signing_test.go | 2 +- das/syncing_fallback_storage.go | 40 +- das/util.go | 4 +- execution/gethexec/arb_interface.go | 17 +- execution/gethexec/block_recorder.go | 4 +- execution/gethexec/blockchain.go | 18 + .../gethexec}/classicMessage.go | 2 +- execution/gethexec/executionengine.go | 380 ++- execution/gethexec/node.go | 52 +- execution/gethexec/sequencer.go | 260 +- execution/gethexec/sync_monitor.go | 120 + execution/gethexec/tx_pre_checker.go | 2 +- execution/gethexec/wasmstorerebuilder.go | 115 + execution/interface.go | 38 +- .../nodeInterface}/NodeInterface.go | 146 +- .../nodeInterface}/NodeInterfaceDebug.go | 0 .../nodeInterface}/virtual-contracts.go | 12 +- fastcache | 2 +- gethhook/geth-hook.go | 28 +- gethhook/geth_test.go | 2 +- go-ethereum | 1 - go.mod | 357 +-- go.sum | 1498 ++--------- nitro-testnode | 1 - precompiles/ArbDebug.go | 7 +- precompiles/ArbGasInfo.go | 56 +- precompiles/ArbInfo.go | 2 +- precompiles/ArbOwner.go | 130 +- precompiles/ArbOwner_test.go | 3 +- precompiles/ArbRetryableTx.go | 2 +- precompiles/ArbSys.go | 4 +- precompiles/ArbWasm.go | 224 ++ precompiles/ArbWasmCache.go | 68 + precompiles/context.go | 16 +- precompiles/precompile.go | 90 +- precompiles/precompile_test.go | 12 +- pubsub/common.go | 29 + pubsub/consumer.go | 175 ++ pubsub/producer.go | 299 +++ pubsub/pubsub_test.go | 336 +++ relay/relay.go | 8 +- scripts/split-val-entry.sh | 19 + solgen/gen.go | 4 +- staker/block_validator.go | 177 +- staker/challenge-cache/cache.go | 242 ++ staker/challenge-cache/cache_test.go | 323 +++ staker/challenge_manager.go | 15 +- staker/challenge_test.go | 11 +- staker/l1_validator.go | 19 +- staker/staker.go | 2 +- staker/stateless_block_validator.go | 295 +-- staker/validatorwallet/contract.go | 1 + system_tests/batch_poster_test.go | 98 +- system_tests/benchmarks_test.go | 64 + system_tests/block_validator_test.go | 28 +- system_tests/blocks_reexecutor_test.go | 26 +- system_tests/common_test.go | 210 +- system_tests/conditionaltx_test.go | 3 +- system_tests/das_test.go | 35 +- system_tests/debug_trace_test.go | 168 ++ system_tests/debugapi_test.go | 6 +- system_tests/estimation_test.go | 3 +- system_tests/eth_sync_test.go | 81 + system_tests/fees_test.go | 11 +- system_tests/full_challenge_impl_test.go | 61 +- system_tests/full_challenge_mock_test.go | 21 + system_tests/full_challenge_test.go | 12 +- system_tests/nodeinterface_test.go | 76 + system_tests/outbox_test.go | 27 + system_tests/precompile_test.go | 2 +- system_tests/program_norace_test.go | 211 ++ system_tests/program_race_test.go | 23 + system_tests/program_recursive_test.go | 195 ++ system_tests/program_test.go | 1658 ++++++++++++ system_tests/pruning_test.go | 5 +- system_tests/recreatestate_rpc_test.go | 2 +- system_tests/retryable_test.go | 36 +- system_tests/seq_coordinator_test.go | 31 +- system_tests/seqfeed_test.go | 114 +- system_tests/seqinbox_test.go | 7 +- system_tests/snap_sync_test.go | 189 ++ system_tests/staker_test.go | 23 +- system_tests/state_fuzz_test.go | 9 +- system_tests/staterecovery_test.go | 5 +- system_tests/stylus_test.go | 110 + system_tests/test_info.go | 3 + system_tests/transfer_test.go | 46 + system_tests/triedb_race_test.go | 2 +- system_tests/validation_mock_test.go | 46 +- system_tests/wrap_transaction_test.go | 16 + util/arbmath/bips.go | 14 +- util/arbmath/bits.go | 94 + util/arbmath/math.go | 175 +- util/arbmath/math_fuzz_test.go | 112 + util/arbmath/math_test.go | 166 +- util/arbmath/time.go | 8 + util/arbmath/uint24.go | 57 + util/colors/colors.go | 19 +- util/headerreader/blob_client.go | 16 +- util/rpcclient/rpcclient.go | 38 +- util/testhelpers/port.go | 17 + util/testhelpers/port_test.go | 23 + util/testhelpers/testhelpers.go | 75 +- validator/client/redis/producer.go | 156 ++ .../validation_client.go | 74 +- validator/interface.go | 1 + validator/server_api/json.go | 128 +- validator/server_arb/machine.go | 60 +- validator/server_arb/nitro_machine.go | 4 +- validator/server_arb/prover_interface.go | 6 +- validator/server_arb/validator_spawner.go | 38 +- validator/server_common/machine_locator.go | 85 +- .../server_common/machine_locator_test.go | 39 + .../module-root.txt | 1 + .../module-root.txt | 1 + .../module-root.txt | 1 + validator/server_common/testdata/latest | 1 + validator/server_jit/jit_machine.go | 23 +- validator/server_jit/machine_loader.go | 5 +- validator/server_jit/spawner.go | 4 + validator/utils.go | 20 + validator/validation_entry.go | 3 + validator/valnode/redis/consumer.go | 157 ++ validator/valnode/redis/consumer_test.go | 30 + .../{server_api => valnode}/validation_api.go | 28 +- validator/valnode/valnode.go | 32 +- wavmio/higher.go | 30 +- wavmio/raw.go | 35 +- wavmio/raw.s | 35 - wavmio/stub.go | 4 +- wsbroadcastserver/clientconnection.go | 2 +- 531 files changed, 49133 insertions(+), 9325 deletions(-) create mode 100644 .github/workflows/merge-checks.yml delete mode 100644 arbcompress/compress_cgo.go delete mode 100644 arbcompress/compress_wasm.go create mode 100644 arbcompress/native.go delete mode 100644 arbcompress/raw.s create mode 100644 arbcompress/wasm.go create mode 100644 arbitrator/arbutil/src/crypto.rs create mode 100644 arbitrator/arbutil/src/evm/api.rs create mode 100644 arbitrator/arbutil/src/evm/mod.rs create mode 100644 arbitrator/arbutil/src/evm/req.rs create mode 100644 arbitrator/arbutil/src/evm/storage.rs create mode 100644 arbitrator/arbutil/src/evm/user.rs create mode 100644 arbitrator/arbutil/src/math.rs create mode 100644 arbitrator/arbutil/src/operator.rs create mode 100644 arbitrator/arbutil/src/pricing.rs create mode 100644 arbitrator/brotli/Cargo.toml create mode 100644 arbitrator/brotli/build.rs create mode 100644 arbitrator/brotli/fuzz/.gitignore create mode 100644 arbitrator/brotli/fuzz/Cargo.toml create mode 100644 arbitrator/brotli/fuzz/README create mode 100644 arbitrator/brotli/fuzz/fuzz_targets/compress.rs create mode 100644 arbitrator/brotli/fuzz/fuzz_targets/decompress.rs create mode 100644 arbitrator/brotli/fuzz/fuzz_targets/round_trip.rs create mode 100644 arbitrator/brotli/src/cgo.rs create mode 100644 arbitrator/brotli/src/dicts/mod.rs create mode 100644 arbitrator/brotli/src/dicts/stylus-program-11.lz create mode 100644 arbitrator/brotli/src/lib.rs create mode 100644 arbitrator/brotli/src/types.rs create mode 100644 arbitrator/brotli/src/wasmer_traits.rs create mode 100644 arbitrator/caller-env/Cargo.toml create mode 100644 arbitrator/caller-env/src/brotli/mod.rs create mode 100644 arbitrator/caller-env/src/guest_ptr.rs create mode 100644 arbitrator/caller-env/src/lib.rs create mode 100644 arbitrator/caller-env/src/static_caller.rs create mode 100644 arbitrator/caller-env/src/wasip1_stub.rs create mode 100644 arbitrator/caller-env/src/wasmer_traits.rs delete mode 100644 arbitrator/cbindgen.toml delete mode 100644 arbitrator/jit/build.rs create mode 100644 arbitrator/jit/src/caller_env.rs delete mode 100644 arbitrator/jit/src/color.rs delete mode 100644 arbitrator/jit/src/gostack.rs create mode 100644 arbitrator/jit/src/program.rs delete mode 100644 arbitrator/jit/src/runtime.rs create mode 100644 arbitrator/jit/src/stylus_backend.rs create mode 100644 arbitrator/jit/src/wasip1_stub.rs create mode 160000 arbitrator/langs/bf create mode 160000 arbitrator/langs/c create mode 160000 arbitrator/langs/rust create mode 100644 arbitrator/prover/src/print.rs create mode 100644 arbitrator/prover/src/programs/config.rs create mode 100644 arbitrator/prover/src/programs/counter.rs create mode 100644 arbitrator/prover/src/programs/depth.rs create mode 100644 arbitrator/prover/src/programs/dynamic.rs create mode 100644 arbitrator/prover/src/programs/heap.rs create mode 100644 arbitrator/prover/src/programs/memory.rs create mode 100644 arbitrator/prover/src/programs/meter.rs create mode 100644 arbitrator/prover/src/programs/mod.rs create mode 100644 arbitrator/prover/src/programs/prelude.rs create mode 100644 arbitrator/prover/src/programs/start.rs create mode 100644 arbitrator/prover/src/test.rs create mode 100644 arbitrator/prover/test-cases/dynamic.wat create mode 100644 arbitrator/prover/test-cases/forward-test.wat create mode 100644 arbitrator/prover/test-cases/forward/forward.wat create mode 100644 arbitrator/prover/test-cases/forward/target.wat create mode 100644 arbitrator/prover/test-cases/global-state-wavm.wat create mode 100644 arbitrator/prover/test-cases/link.txt create mode 100644 arbitrator/prover/test-cases/link.wat create mode 100644 arbitrator/prover/test-cases/user.wat create mode 100644 arbitrator/stylus/Cargo.toml create mode 100644 arbitrator/stylus/cbindgen.toml create mode 100644 arbitrator/stylus/src/benchmarks.rs create mode 100644 arbitrator/stylus/src/cache.rs create mode 100644 arbitrator/stylus/src/env.rs create mode 100644 arbitrator/stylus/src/evm_api.rs create mode 100644 arbitrator/stylus/src/host.rs create mode 100644 arbitrator/stylus/src/lib.rs create mode 100644 arbitrator/stylus/src/native.rs create mode 100644 arbitrator/stylus/src/run.rs create mode 100644 arbitrator/stylus/src/test/api.rs create mode 100644 arbitrator/stylus/src/test/misc.rs create mode 100644 arbitrator/stylus/src/test/mod.rs create mode 100644 arbitrator/stylus/src/test/native.rs create mode 100644 arbitrator/stylus/src/test/sdk.rs create mode 100644 arbitrator/stylus/src/test/timings.rs create mode 100644 arbitrator/stylus/src/test/wavm.rs create mode 100644 arbitrator/stylus/src/util.rs create mode 100644 arbitrator/stylus/tests/.cargo/config.toml create mode 100644 arbitrator/stylus/tests/add.wat create mode 100644 arbitrator/stylus/tests/bad-mods/bad-export.wat create mode 100644 arbitrator/stylus/tests/bad-mods/bad-export2.wat create mode 100644 arbitrator/stylus/tests/bad-mods/bad-export3.wat create mode 100644 arbitrator/stylus/tests/bad-mods/bad-export4.wat create mode 100644 arbitrator/stylus/tests/bad-mods/bad-import.wat create mode 100644 arbitrator/stylus/tests/bf/.gitignore create mode 100644 arbitrator/stylus/tests/bf/cat.b create mode 100644 arbitrator/stylus/tests/bulk-memory-oob.wat create mode 100644 arbitrator/stylus/tests/clz.wat create mode 100644 arbitrator/stylus/tests/console.wat create mode 100644 arbitrator/stylus/tests/create/.cargo/config create mode 100644 arbitrator/stylus/tests/create/Cargo.lock create mode 100644 arbitrator/stylus/tests/create/Cargo.toml create mode 100644 arbitrator/stylus/tests/create/src/main.rs create mode 100644 arbitrator/stylus/tests/depth.wat create mode 100644 arbitrator/stylus/tests/erc20/Cargo.lock create mode 100644 arbitrator/stylus/tests/erc20/Cargo.toml create mode 100644 arbitrator/stylus/tests/erc20/src/erc20.rs create mode 100644 arbitrator/stylus/tests/erc20/src/main.rs create mode 100644 arbitrator/stylus/tests/evm-data/.cargo/config create mode 100644 arbitrator/stylus/tests/evm-data/Cargo.lock create mode 100644 arbitrator/stylus/tests/evm-data/Cargo.toml create mode 100644 arbitrator/stylus/tests/evm-data/src/main.rs create mode 100644 arbitrator/stylus/tests/exit-early/exit-early.wat create mode 100644 arbitrator/stylus/tests/exit-early/panic-after-write.wat create mode 100644 arbitrator/stylus/tests/fallible/.cargo/config create mode 100644 arbitrator/stylus/tests/fallible/Cargo.lock create mode 100644 arbitrator/stylus/tests/fallible/Cargo.toml create mode 100644 arbitrator/stylus/tests/fallible/src/main.rs create mode 100644 arbitrator/stylus/tests/grow/fixed.wat create mode 100644 arbitrator/stylus/tests/grow/grow-120.wat create mode 100644 arbitrator/stylus/tests/grow/grow-and-call.wat create mode 100644 arbitrator/stylus/tests/grow/mem-write.wat create mode 100644 arbitrator/stylus/tests/keccak-100/Cargo.lock create mode 100644 arbitrator/stylus/tests/keccak-100/Cargo.toml create mode 100644 arbitrator/stylus/tests/keccak-100/src/main.rs create mode 100644 arbitrator/stylus/tests/keccak/Cargo.lock create mode 100644 arbitrator/stylus/tests/keccak/Cargo.toml create mode 100644 arbitrator/stylus/tests/keccak/src/main.rs create mode 100644 arbitrator/stylus/tests/log/.cargo/config create mode 100644 arbitrator/stylus/tests/log/Cargo.lock create mode 100644 arbitrator/stylus/tests/log/Cargo.toml create mode 100644 arbitrator/stylus/tests/log/src/main.rs create mode 100644 arbitrator/stylus/tests/math/Cargo.lock create mode 100644 arbitrator/stylus/tests/math/Cargo.toml create mode 100644 arbitrator/stylus/tests/math/src/main.rs create mode 100644 arbitrator/stylus/tests/memory.wat create mode 100644 arbitrator/stylus/tests/memory2.wat create mode 100644 arbitrator/stylus/tests/module-mod.wat create mode 100644 arbitrator/stylus/tests/multicall/.cargo/config create mode 100644 arbitrator/stylus/tests/multicall/Cargo.lock create mode 100644 arbitrator/stylus/tests/multicall/Cargo.toml create mode 100644 arbitrator/stylus/tests/multicall/src/main.rs create mode 100644 arbitrator/stylus/tests/read-return-data/.cargo/config create mode 100644 arbitrator/stylus/tests/read-return-data/Cargo.lock create mode 100644 arbitrator/stylus/tests/read-return-data/Cargo.toml create mode 100644 arbitrator/stylus/tests/read-return-data/src/main.rs create mode 100644 arbitrator/stylus/tests/sdk-storage/.cargo/config create mode 100644 arbitrator/stylus/tests/sdk-storage/Cargo.lock create mode 100644 arbitrator/stylus/tests/sdk-storage/Cargo.toml create mode 100644 arbitrator/stylus/tests/sdk-storage/src/main.rs create mode 100644 arbitrator/stylus/tests/start.wat create mode 100644 arbitrator/stylus/tests/storage/.cargo/config create mode 100644 arbitrator/stylus/tests/storage/Cargo.lock create mode 100644 arbitrator/stylus/tests/storage/Cargo.toml create mode 100644 arbitrator/stylus/tests/storage/src/main.rs create mode 100644 arbitrator/stylus/tests/test.wat create mode 100644 arbitrator/stylus/tests/timings/Cargo.lock create mode 100644 arbitrator/stylus/tests/timings/block_basefee.wat create mode 100644 arbitrator/stylus/tests/timings/block_coinbase.wat create mode 100644 arbitrator/stylus/tests/timings/block_gas_limit.wat create mode 100644 arbitrator/stylus/tests/timings/block_number.wat create mode 100644 arbitrator/stylus/tests/timings/block_timestamp.wat create mode 100644 arbitrator/stylus/tests/timings/chainid.wat create mode 100644 arbitrator/stylus/tests/timings/contract_address.wat create mode 100644 arbitrator/stylus/tests/timings/evm_gas_left.wat create mode 100644 arbitrator/stylus/tests/timings/evm_ink_left.wat create mode 100644 arbitrator/stylus/tests/timings/keccak.wat create mode 100644 arbitrator/stylus/tests/timings/msg_sender.wat create mode 100644 arbitrator/stylus/tests/timings/msg_value.wat create mode 100644 arbitrator/stylus/tests/timings/null_host.wat create mode 100644 arbitrator/stylus/tests/timings/read_args.wat create mode 100644 arbitrator/stylus/tests/timings/return_data_size.wat create mode 100644 arbitrator/stylus/tests/timings/tx_gas_price.wat create mode 100644 arbitrator/stylus/tests/timings/tx_ink_price.wat create mode 100644 arbitrator/stylus/tests/timings/tx_origin.wat create mode 100644 arbitrator/stylus/tests/timings/write_result.wat create mode 100644 arbitrator/tools/module_roots/Cargo.lock create mode 100644 arbitrator/tools/module_roots/Cargo.toml create mode 100644 arbitrator/tools/module_roots/src/main.rs create mode 160000 arbitrator/tools/wasmer create mode 100644 arbitrator/wasm-libraries/arbcompress/Cargo.toml rename arbitrator/wasm-libraries/{brotli => arbcompress}/build.rs (65%) create mode 100644 arbitrator/wasm-libraries/arbcompress/src/lib.rs delete mode 100644 arbitrator/wasm-libraries/brotli/src/lib.rs create mode 100644 arbitrator/wasm-libraries/forward/.gitignore create mode 100644 arbitrator/wasm-libraries/forward/Cargo.toml create mode 100644 arbitrator/wasm-libraries/forward/src/main.rs delete mode 100644 arbitrator/wasm-libraries/go-abi/Cargo.toml delete mode 100644 arbitrator/wasm-libraries/go-abi/src/lib.rs delete mode 100644 arbitrator/wasm-libraries/go-stub/Cargo.toml delete mode 100644 arbitrator/wasm-libraries/go-stub/src/lib.rs rename arbitrator/wasm-libraries/{brotli => program-exec}/Cargo.toml (58%) create mode 100644 arbitrator/wasm-libraries/program-exec/src/lib.rs create mode 100644 arbitrator/wasm-libraries/user-host-trait/Cargo.toml create mode 100644 arbitrator/wasm-libraries/user-host-trait/src/lib.rs create mode 100644 arbitrator/wasm-libraries/user-host/Cargo.toml create mode 100644 arbitrator/wasm-libraries/user-host/src/host.rs create mode 100644 arbitrator/wasm-libraries/user-host/src/ink.rs create mode 100644 arbitrator/wasm-libraries/user-host/src/lib.rs create mode 100644 arbitrator/wasm-libraries/user-host/src/link.rs create mode 100644 arbitrator/wasm-libraries/user-host/src/program.rs create mode 100644 arbitrator/wasm-libraries/user-test/Cargo.toml create mode 100644 arbitrator/wasm-libraries/user-test/src/host.rs create mode 100644 arbitrator/wasm-libraries/user-test/src/ink.rs create mode 100644 arbitrator/wasm-libraries/user-test/src/lib.rs create mode 100644 arbitrator/wasm-libraries/user-test/src/program.rs delete mode 100644 arbnode/dataposter/externalsigner/externalsigner.go delete mode 100644 arbnode/dataposter/externalsigner/externalsigner_test.go create mode 100644 arbos/activate_test.go create mode 100644 arbos/programs/api.go create mode 100644 arbos/programs/constant_test.go create mode 100644 arbos/programs/data_pricer.go create mode 100644 arbos/programs/memory.go create mode 100644 arbos/programs/memory_test.go create mode 100644 arbos/programs/native.go create mode 100644 arbos/programs/native_api.go create mode 100644 arbos/programs/params.go create mode 100644 arbos/programs/programs.go create mode 100644 arbos/programs/testconstants.go create mode 100644 arbos/programs/wasm.go create mode 100644 arbos/programs/wasm_api.go create mode 100644 arbos/programs/wasmstorehelper.go create mode 100644 arbstate/daprovider/reader.go rename arbstate/{das_reader.go => daprovider/util.go} (62%) create mode 100644 arbstate/daprovider/writer.go create mode 100644 arbutil/format.go create mode 100644 arbutil/unsafe.go create mode 100644 cmd/genericconf/loglevel.go rename cmd/ipfshelper/{ipfshelper.go => ipfshelper.bkup_go} (99%) create mode 100644 cmd/ipfshelper/ipfshelper_stub.go create mode 100644 cmd/nitro/init_test.go delete mode 160000 contracts delete mode 100644 das/ipfs_storage_service.go delete mode 100644 das/ipfs_storage_service_test.go delete mode 100644 das/iterable_storage_service.go delete mode 100644 das/regular_sync_storage_test.go delete mode 100644 das/regularly_sync_storage.go create mode 100644 das/signature_verifier.go rename {arbnode => execution/gethexec}/classicMessage.go (99%) create mode 100644 execution/gethexec/sync_monitor.go create mode 100644 execution/gethexec/wasmstorerebuilder.go rename {nodeInterface => execution/nodeInterface}/NodeInterface.go (88%) rename {nodeInterface => execution/nodeInterface}/NodeInterfaceDebug.go (100%) rename {nodeInterface => execution/nodeInterface}/virtual-contracts.go (94%) delete mode 160000 go-ethereum delete mode 160000 nitro-testnode create mode 100644 precompiles/ArbWasm.go create mode 100644 precompiles/ArbWasmCache.go create mode 100644 pubsub/common.go create mode 100644 pubsub/consumer.go create mode 100644 pubsub/producer.go create mode 100644 pubsub/pubsub_test.go create mode 100755 scripts/split-val-entry.sh create mode 100644 staker/challenge-cache/cache.go create mode 100644 staker/challenge-cache/cache_test.go create mode 100644 system_tests/benchmarks_test.go create mode 100644 system_tests/debug_trace_test.go create mode 100644 system_tests/eth_sync_test.go create mode 100644 system_tests/full_challenge_mock_test.go create mode 100644 system_tests/program_norace_test.go create mode 100644 system_tests/program_race_test.go create mode 100644 system_tests/program_recursive_test.go create mode 100644 system_tests/program_test.go create mode 100644 system_tests/snap_sync_test.go create mode 100644 system_tests/stylus_test.go create mode 100644 util/arbmath/math_fuzz_test.go create mode 100644 util/arbmath/time.go create mode 100644 util/arbmath/uint24.go create mode 100644 util/testhelpers/port.go create mode 100644 util/testhelpers/port_test.go create mode 100644 validator/client/redis/producer.go rename validator/{server_api => client}/validation_client.go (69%) create mode 100644 validator/server_common/machine_locator_test.go create mode 100644 validator/server_common/testdata/0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4/module-root.txt create mode 100644 validator/server_common/testdata/0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4/module-root.txt create mode 100644 validator/server_common/testdata/0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a/module-root.txt create mode 120000 validator/server_common/testdata/latest create mode 100644 validator/utils.go create mode 100644 validator/valnode/redis/consumer.go create mode 100644 validator/valnode/redis/consumer_test.go rename validator/{server_api => valnode}/validation_api.go (82%) delete mode 100644 wavmio/raw.s diff --git a/.dockerignore b/.dockerignore index 763aeda1b..e142afd07 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,10 +22,21 @@ brotli/buildfiles/**/* nitro-testnode/**/* # Arbitrator ignores +arbitrator/tools/module_roots +arbitrator/tools/pricer # Rust outputs arbitrator/target/**/* arbitrator/target +arbitrator/stylus/tests/*/target/ +arbitrator/wasm-testsuite/target/ +arbitrator/wasm-libraries/target/ +arbitrator/tools/wasmer/target/ +arbitrator/tools/wasm-tools/ +arbitrator/tools/pricers/ +arbitrator/tools/module_roots/ +arbitrator/langs/rust/target/ +arbitrator/langs/bf/target/ # Compiled files **/*.o diff --git a/.github/workflows/arbitrator-ci.yml b/.github/workflows/arbitrator-ci.yml index c203bba67..a39306a6c 100644 --- a/.github/workflows/arbitrator-ci.yml +++ b/.github/workflows/arbitrator-ci.yml @@ -3,6 +3,12 @@ run-name: Arbitrator CI triggered from @${{ github.actor }} of ${{ github.head_r on: workflow_dispatch: + inputs: + enable_tmate: + type: boolean + description: 'Enable tmate' + required: false + default: false merge_group: pull_request: paths: @@ -16,7 +22,7 @@ on: env: RUST_BACKTRACE: 1 - RUSTFLAGS: -Dwarnings +# RUSTFLAGS: -Dwarnings # TODO: re-enable after wasmer upgrade WABT_VERSION: 1.0.32 jobs: @@ -24,6 +30,12 @@ jobs: name: Run Arbitrator tests runs-on: linux-2xl steps: + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && inputs.enable_tmate }} + with: + detached: true + - name: Checkout uses: actions/checkout@v4 with: @@ -40,7 +52,7 @@ jobs: - name: Install go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Install custom go-ethereum run: | @@ -61,8 +73,16 @@ jobs: - name: Install rust stable uses: dtolnay/rust-toolchain@stable with: + toolchain: "1.76" components: 'llvm-tools-preview, rustfmt, clippy' + + - name: Install rust nightly + uses: dtolnay/rust-toolchain@nightly + id: install-rust-nightly + with: + toolchain: "nightly-2024-02-04" targets: 'wasm32-wasi, wasm32-unknown-unknown' + components: 'rust-src, rustfmt, clippy' - name: Cache Rust intermediate build products uses: actions/cache@v3 @@ -138,18 +158,28 @@ jobs: run: echo "$HOME/wabt-prefix/bin" >> "$GITHUB_PATH" - name: Make arbitrator libraries - run: make -j wasm-ci-build - - # TODO: Enable clippy check - # - name: Clippy check - # run: cargo clippy --all --manifest-path arbitrator/Cargo.toml -- -D warnings + run: make -j wasm-ci-build STYLUS_NIGHTLY_VER="+nightly-2024-02-04" + + - name: Clippy check + run: cargo clippy --all --manifest-path arbitrator/Cargo.toml -- -D warnings - name: Run rust tests - run: cargo test --all --exclude rust-kzg-bn254 --manifest-path arbitrator/Cargo.toml + uses: actions-rs/cargo@v1 + with: + command: test + args: -p arbutil -p prover -p jit -p stylus --release --manifest-path arbitrator/prover/Cargo.toml + + - name: Rustfmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -p arbutil -p prover -p jit -p stylus --manifest-path arbitrator/Cargo.toml -- --check - # TODO: Enable rustfmt check - # - name: Rustfmt - # run: cargo fmt --all --exclude rust-kzg-bn254 --manifest-path arbitrator/Cargo.toml -- --check + - name: Rustfmt - langs/rust + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all --manifest-path arbitrator/langs/rust/Cargo.toml -- --check - name: Make proofs from test cases run: make -j test-gen-proofs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e6bb4694..2931fa956 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - test-mode: [defaults, race, challenge] + test-mode: [defaults, race, challenge, stylus, long] steps: - name: Checkout @@ -47,7 +47,7 @@ jobs: - name: Install go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Install wasm-ld run: | @@ -62,6 +62,21 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Install rust nightly + uses: actions-rs/toolchain@v1 + id: install-rust-nightly + with: + profile: minimal + toolchain: "nightly" + + - name: Install rust wasm targets + run: rustup target add wasm32-wasi wasm32-unknown-unknown + + - name: Install nightly wasm targets + run: | + rustup component add rust-src --toolchain nightly + rustup target add wasm32-unknown-unknown --toolchain nightly + - name: Cache Build Products uses: actions/cache@v3 with: @@ -131,13 +146,13 @@ jobs: if: matrix.test-mode == 'defaults' run: | packages=`go list ./...` - gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -timeout 20m + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -timeout 20m -parallel=8 > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") - name: run tests with race detection if: matrix.test-mode == 'race' - run: | + run: | packages=`go list ./...` - gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- -race -timeout 30m + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -race -timeout 30m -parallel=8 > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") - name: run redis tests if: matrix.test-mode == 'defaults' @@ -145,9 +160,27 @@ jobs: - name: run challenge tests if: matrix.test-mode == 'challenge' - run: | + run: | packages=`go list ./...` - gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=challengetest -run=TestChallenge + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=challengetest -run=TestChallenge > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") + + - name: run stylus tests + if: matrix.test-mode == 'stylus' + run: | + packages=`go list ./...` + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=stylustest -run="TestProgramArbitrator" > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") + + - name: run long stylus tests + if: matrix.test-mode == 'long' + run: | + packages=`go list ./...` + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=stylustest -run="TestProgramLong" > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") + + - name: Archive detailed run log + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.test-mode }}-full.log + path: full.log - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1869f943e..4d4d9577b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,6 +48,9 @@ jobs: submodules: true ssh-key: ${{ secrets.SSH_KEY }} + - name: Install dependencies + run: sudo apt update && sudo apt install -y wabt + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 @@ -71,7 +74,7 @@ jobs: - name: Install go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x - name: Install rust stable uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/merge-checks.yml b/.github/workflows/merge-checks.yml new file mode 100644 index 000000000..6f291bbb2 --- /dev/null +++ b/.github/workflows/merge-checks.yml @@ -0,0 +1,20 @@ +name: Merge Checks + +on: + pull_request: + branches: [ master ] + types: [synchronize, opened, reopened, labeled, unlabeled] + +jobs: + design-approved-check: + if: ${{ !contains(github.event.*.labels.*.name, 'design-approved') }} + name: Design Approved Check + runs-on: ubuntu-latest + steps: + - name: Check for design-approved label + run: | + echo "Pull request is missing the 'design-approved' label" + echo "This workflow fails so that the pull request cannot be merged" + exit 1 + + diff --git a/.gitignore b/.gitignore index 8a628e29c..b94d61e74 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,6 @@ target/ yarn-error.log local/ system_tests/test-data/* +.configs/ system_tests/testdata/* arbos/testdata/* diff --git a/.gitmodules b/.gitmodules index 3421a1624..4c2b93550 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,4 +22,16 @@ [submodule "arbitrator/rust-kzg-bn254"] path = arbitrator/rust-kzg-bn254 url = https://github.com/Layr-Labs/rust-kzg-bn254.git - branch = epociask--better-linting \ No newline at end of file + branch = epociask--better-linting +[submodule "arbitrator/tools/wasmer"] + path = arbitrator/tools/wasmer + url = https://github.com/OffchainLabs/wasmer.git +[submodule "arbitrator/langs/rust"] + path = arbitrator/langs/rust + url = https://github.com/OffchainLabs/stylus-sdk-rs.git +[submodule "arbitrator/langs/c"] + path = arbitrator/langs/c + url = https://github.com/OffchainLabs/stylus-sdk-c.git +[submodule "arbitrator/langs/bf"] + path = arbitrator/langs/bf + url = https://github.com/OffchainLabs/stylus-sdk-bf.git diff --git a/.golangci.yml b/.golangci.yml index e794cdb84..059467013 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,12 @@ # golangci-lint configuration run: - skip-dirs: - - go-ethereum - - fastcache - timeout: 10m issues: + exclude-dirs: + - go-ethereum + - fastcache exclude-rules: - path: _test\.go linters: diff --git a/Dockerfile b/Dockerfile index 408a04cb5..f46101d9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,23 +41,31 @@ RUN apt-get update && apt-get install -y curl build-essential=12.9 FROM wasm-base as wasm-libs-builder # clang / lld used by soft-float wasm -RUN apt-get install -y clang=1:14.0-55.7~deb12u1 lld=1:14.0-55.7~deb12u1 - # pinned rust 1.70.0 -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.70.0 --target x86_64-unknown-linux-gnu wasm32-unknown-unknown wasm32-wasi +RUN apt-get update && \ + apt-get install -y clang=1:14.0-55.7~deb12u1 lld=1:14.0-55.7~deb12u1 wabt + # pinned rust 1.75.0 +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.75.0 --target x86_64-unknown-linux-gnu wasm32-unknown-unknown wasm32-wasi COPY ./Makefile ./ -COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/rust-kzg-bn254 arbitrator/rust-kzg-bn254 +COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/brotli arbitrator/brotli +COPY arbitrator/caller-env arbitrator/caller-env +COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries - +COPY arbitrator/tools/wasmer arbitrator/tools/wasmer +COPY brotli brotli +COPY scripts/build-brotli.sh scripts/ COPY --from=brotli-wasm-export / target/ +RUN apt-get update && apt-get install -y cmake RUN . ~/.cargo/env && NITRO_BUILD_IGNORE_TIMESTAMPS=1 RUSTFLAGS='-C symbol-mangling-version=v0' make build-wasm-libs FROM scratch as wasm-libs-export COPY --from=wasm-libs-builder /workspace/ / FROM wasm-base as wasm-bin-builder - # pinned go version -RUN curl -L https://golang.org/dl/go1.20.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf - +# pinned go version +RUN curl -L https://golang.org/dl/go1.21.10.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf - COPY ./Makefile ./go.mod ./go.sum ./ COPY ./arbcompress ./arbcompress COPY ./arbos ./arbos @@ -85,18 +93,27 @@ COPY --from=contracts-builder workspace/contracts/node_modules/@offchainlabs/upg COPY --from=contracts-builder workspace/.make/ .make/ RUN PATH="$PATH:/usr/local/go/bin" NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-wasm-bin -FROM rust:1.70-slim-bookworm as prover-header-builder +FROM rust:1.75-slim-bookworm as prover-header-builder WORKDIR /workspace RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ - apt-get install -y make clang && \ + apt-get install -y make clang wabt && \ cargo install --force cbindgen -COPY arbitrator/Cargo.* arbitrator/cbindgen.toml arbitrator/ +COPY arbitrator/Cargo.* arbitrator/ COPY ./Makefile ./ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/brotli arbitrator/brotli +COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover arbitrator/prover +COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/jit arbitrator/jit COPY arbitrator/rust-kzg-bn254 arbitrator/rust-kzg-bn254 +COPY arbitrator/stylus arbitrator/stylus +COPY arbitrator/tools/wasmer arbitrator/tools/wasmer +COPY --from=brotli-wasm-export / target/ +COPY scripts/build-brotli.sh scripts/ +COPY brotli brotli +RUN apt-get update && apt-get install -y cmake RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-header FROM scratch as prover-header-export @@ -106,29 +123,45 @@ FROM rust:1.75-slim-bookworm as prover-builder WORKDIR /workspace RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ - apt-get install -y make wget gpg software-properties-common zlib1g-dev \ - libstdc++-11-dev wabt clang llvm-dev libclang-common-14-dev libpolly-14-dev + apt-get install -y make wget gpg software-properties-common zlib1g-dev libstdc++-12-dev wabt +RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ + add-apt-repository 'deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-15 main' && \ + apt-get update && \ + apt-get install -y llvm-15-dev libclang-common-15-dev +COPY --from=brotli-library-export / target/ COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/brotli arbitrator/brotli +COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover/Cargo.toml arbitrator/prover/ COPY arbitrator/jit/Cargo.toml arbitrator/jit/ COPY arbitrator/rust-kzg-bn254 arbitrator/rust-kzg-bn254 -RUN mkdir arbitrator/prover/src arbitrator/jit/src && \ - echo "fn test() {}" > arbitrator/jit/src/lib.rs && \ +COPY arbitrator/stylus/Cargo.toml arbitrator/stylus/ +COPY arbitrator/tools/wasmer arbitrator/tools/wasmer +COPY arbitrator/wasm-libraries/user-host-trait/Cargo.toml arbitrator/wasm-libraries/user-host-trait/Cargo.toml +RUN bash -c 'mkdir arbitrator/{prover,jit,stylus}/src arbitrator/wasm-libraries/user-host-trait/src' +RUN echo "fn test() {}" > arbitrator/jit/src/lib.rs && \ echo "fn test() {}" > arbitrator/prover/src/lib.rs && \ + echo "fn test() {}" > arbitrator/stylus/src/lib.rs && \ + echo "fn test() {}" > arbitrator/wasm-libraries/user-host-trait/src/lib.rs && \ cargo build --manifest-path arbitrator/Cargo.toml --release --lib && \ - rm arbitrator/jit/src/lib.rs + rm arbitrator/prover/src/lib.rs arbitrator/jit/src/lib.rs arbitrator/stylus/src/lib.rs && \ + rm arbitrator/wasm-libraries/user-host-trait/src/lib.rs COPY ./Makefile ./ COPY arbitrator/prover arbitrator/prover +COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/jit arbitrator/jit COPY arbitrator/rust-kzg-bn254 arbitrator/rust-kzg-bn254 -COPY --from=brotli-library-export / target/ +COPY arbitrator/stylus arbitrator/stylus +COPY --from=brotli-wasm-export / target/ +COPY scripts/build-brotli.sh scripts/ +COPY brotli brotli RUN touch -a -m arbitrator/prover/src/lib.rs RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-lib RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-bin -RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make CARGOFLAGS="--features=llvm" build-jit +RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-jit FROM scratch as prover-export COPY --from=prover-builder /workspace/target/ / @@ -142,7 +175,12 @@ COPY --from=prover-export / target/ COPY --from=wasm-bin-builder /workspace/target/ target/ COPY --from=wasm-bin-builder /workspace/.make/ .make/ COPY --from=wasm-libs-builder /workspace/target/ target/ +COPY --from=wasm-libs-builder /workspace/arbitrator/prover/ arbitrator/prover/ +COPY --from=wasm-libs-builder /workspace/arbitrator/tools/wasmer/ arbitrator/tools/wasmer/ COPY --from=wasm-libs-builder /workspace/arbitrator/wasm-libraries/ arbitrator/wasm-libraries/ +COPY --from=wasm-libs-builder /workspace/arbitrator/arbutil arbitrator/arbutil +COPY --from=wasm-libs-builder /workspace/arbitrator/brotli arbitrator/brotli +COPY --from=wasm-libs-builder /workspace/arbitrator/caller-env arbitrator/caller-env COPY --from=wasm-libs-builder /workspace/.make/ .make/ COPY ./Makefile ./ COPY ./arbitrator ./arbitrator @@ -166,15 +204,16 @@ COPY ./scripts/download-machine.sh . #RUN ./download-machine.sh consensus-v7 0x53dd4b9a3d807a8cbb4d58fbfc6a0857c3846d46956848cae0a1cc7eca2bb5a8 #RUN ./download-machine.sh consensus-v7.1 0x2b20e1490d1b06299b222f3239b0ae07e750d8f3b4dedd19f500a815c1548bbc #RUN ./download-machine.sh consensus-v9 0xd1842bfbe047322b3f3b3635b5fe62eb611557784d17ac1d2b1ce9c170af6544 -RUN ./download-machine.sh consensus-v10 0x6b94a7fc388fd8ef3def759297828dc311761e88d8179c7ee8d3887dc554f3c3 -RUN ./download-machine.sh consensus-v10.1 0xda4e3ad5e7feacb817c21c8d0220da7650fe9051ece68a3f0b1c5d38bbb27b21 -RUN ./download-machine.sh consensus-v10.2 0x0754e09320c381566cc0449904c377a52bd34a6b9404432e80afd573b67f7b17 -RUN ./download-machine.sh consensus-v10.3 0xf559b6d4fa869472dabce70fe1c15221bdda837533dfd891916836975b434dec -RUN ./download-machine.sh consensus-v11 0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a -RUN ./download-machine.sh consensus-v11.1 0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4 -RUN ./download-machine.sh consensus-v20 0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4 +#RUN ./download-machine.sh consensus-v10 0x6b94a7fc388fd8ef3def759297828dc311761e88d8179c7ee8d3887dc554f3c3 +#RUN ./download-machine.sh consensus-v10.1 0xda4e3ad5e7feacb817c21c8d0220da7650fe9051ece68a3f0b1c5d38bbb27b21 +#RUN ./download-machine.sh consensus-v10.2 0x0754e09320c381566cc0449904c377a52bd34a6b9404432e80afd573b67f7b17 +#RUN ./download-machine.sh consensus-v10.3 0xf559b6d4fa869472dabce70fe1c15221bdda837533dfd891916836975b434dec +#RUN ./download-machine.sh consensus-v11 0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a +#RUN ./download-machine.sh consensus-v11.1 0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4 +#RUN ./download-machine.sh consensus-v20 0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4 +RUN ./download-machine.sh consensus-v30 0xb0de9cb89e4d944ae6023a3b62276e54804c242fd8c4c2d8e6cc4450f5fa8b1b && true -FROM golang:1.20-bookworm as node-builder +FROM golang:1.21.10-bookworm as node-builder WORKDIR /workspace ARG version="" ARG datetime="" @@ -238,11 +277,15 @@ USER user WORKDIR /home/user/ ENTRYPOINT [ "/usr/local/bin/nitro" ] +FROM offchainlabs/nitro-node:v2.3.4-rc.5-b4cc111 as nitro-legacy + FROM nitro-node-slim as nitro-node USER root COPY --from=prover-export /bin/jit /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/daserver /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/datool /usr/local/bin/ +COPY --from=nitro-legacy /home/user/target/machines /home/user/nitro-legacy/machines +RUN rm -rf /workspace/target/legacy-machines/latest RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ apt-get install -y \ @@ -252,10 +295,23 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /usr/share/doc/* /var/cache/ldconfig/aux-cache /usr/lib/python3.9/__pycache__/ /usr/lib/python3.9/*/__pycache__/ /var/log/* && \ nitro --version +ENTRYPOINT [ "/usr/local/bin/nitro" , "--validation.wasm.allowed-wasm-module-roots", "/home/user/nitro-legacy/machines,/home/user/target/machines"] + +USER user +FROM nitro-node as nitro-node-validator +USER root +COPY --from=nitro-legacy /usr/local/bin/nitro-val /home/user/nitro-legacy/bin/nitro-val +COPY --from=nitro-legacy /usr/local/bin/jit /home/user/nitro-legacy/bin/jit +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -y xxd netcat-traditional && \ + rm -rf /var/lib/apt/lists/* /usr/share/doc/* /var/cache/ldconfig/aux-cache /usr/lib/python3.9/__pycache__/ /usr/lib/python3.9/*/__pycache__/ /var/log/* +COPY scripts/split-val-entry.sh /usr/local/bin +ENTRYPOINT [ "/usr/local/bin/split-val-entry.sh" ] USER user -FROM nitro-node as nitro-node-dev +FROM nitro-node-validator as nitro-node-dev USER root # Copy in latest WASM module root RUN rm -f /home/user/target/machines/latest diff --git a/Makefile b/Makefile index d03b94072..53b89c8d7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -# Copyright 2021-2022, Offchain Labs, Inc. -# For license information, see https://github.com/nitro/blob/master/LICENSE +# Copyright 2021-2024, Offchain Labs, Inc. +# For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE # Docker builds mess up file timestamps. Then again, in docker builds we never # have to update an existing file. So - for docker, convert all dependencies @@ -28,31 +28,33 @@ ifneq ($(origin NITRO_MODIFIED),undefined) endif ifneq ($(origin GOLANG_LDFLAGS),undefined) - GOLANG_PARAMS = -ldflags="$(GOLANG_LDFLAGS)" + GOLANG_PARAMS = -ldflags="-extldflags '-ldl' $(GOLANG_LDFLAGS)" endif precompile_names = AddressTable Aggregator BLS Debug FunctionTable GasInfo Info osTest Owner RetryableTx Statistics Sys precompiles = $(patsubst %,./solgen/generated/%.go, $(precompile_names)) output_root=target +output_latest=$(output_root)/machines/latest -repo_dirs = arbos arbnode arbstate cmd precompiles solgen system_tests util validator wavmio -go_source = $(wildcard $(patsubst %,%/*.go, $(repo_dirs)) $(patsubst %,%/*/*.go, $(repo_dirs))) +repo_dirs = arbos arbcompress arbnode arbutil arbstate cmd das precompiles solgen system_tests util validator wavmio +go_source.go = $(wildcard $(patsubst %,%/*.go, $(repo_dirs)) $(patsubst %,%/*/*.go, $(repo_dirs))) +go_source.s = $(wildcard $(patsubst %,%/*.s, $(repo_dirs)) $(patsubst %,%/*/*.s, $(repo_dirs))) +go_source = $(go_source.go) $(go_source.s) color_pink = "\e[38;5;161;1m" color_reset = "\e[0;0m" done = "%bdone!%b\n" $(color_pink) $(color_reset) -replay_deps=arbos wavmio arbstate arbcompress solgen/go/node-interfacegen blsSignatures cmd/replay +replay_wasm=$(output_latest)/replay.wasm -replay_wasm=$(output_root)/machines/latest/replay.wasm +arb_brotli_files = $(wildcard arbitrator/brotli/src/*.* arbitrator/brotli/src/*/*.* arbitrator/brotli/*.toml arbitrator/brotli/*.rs) .make/cbrotli-lib .make/cbrotli-wasm arbitrator_generated_header=$(output_root)/include/arbitrator.h -arbitrator_wasm_libs_nogo=$(output_root)/machines/latest/wasi_stub.wasm $(output_root)/machines/latest/host_io.wasm $(output_root)/machines/latest/soft-float.wasm -arbitrator_wasm_libs=$(arbitrator_wasm_libs_nogo) $(patsubst %,$(output_root)/machines/latest/%.wasm, go_stub brotli) -arbitrator_prover_lib=$(output_root)/lib/libprover.a -arbitrator_prover_bin=$(output_root)/bin/prover +arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, forward wasi_stub host_io soft-float arbcompress user_host program_exec) +arbitrator_stylus_lib=$(output_root)/lib/libstylus.a +prover_bin=$(output_root)/bin/prover arbitrator_jit=$(output_root)/bin/jit arbitrator_cases=arbitrator/prover/test-cases @@ -60,24 +62,87 @@ arbitrator_cases=arbitrator/prover/test-cases arbitrator_tests_wat=$(wildcard $(arbitrator_cases)/*.wat) arbitrator_tests_rust=$(wildcard $(arbitrator_cases)/rust/src/bin/*.rs) -arbitrator_test_wasms=$(patsubst %.wat,%.wasm, $(arbitrator_tests_wat)) $(patsubst $(arbitrator_cases)/rust/src/bin/%.rs,$(arbitrator_cases)/rust/target/wasm32-wasi/release/%.wasm, $(arbitrator_tests_rust)) $(arbitrator_cases)/go/main +arbitrator_test_wasms=$(patsubst %.wat,%.wasm, $(arbitrator_tests_wat)) $(patsubst $(arbitrator_cases)/rust/src/bin/%.rs,$(arbitrator_cases)/rust/target/wasm32-wasi/release/%.wasm, $(arbitrator_tests_rust)) $(arbitrator_cases)/go/testcase.wasm + +arbitrator_tests_link_info = $(shell cat $(arbitrator_cases)/link.txt | xargs) +arbitrator_tests_link_deps = $(patsubst %,$(arbitrator_cases)/%.wasm, $(arbitrator_tests_link_info)) + +arbitrator_tests_forward_wats = $(wildcard $(arbitrator_cases)/forward/*.wat) +arbitrator_tests_forward_deps = $(arbitrator_tests_forward_wats:wat=wasm) WASI_SYSROOT?=/opt/wasi-sdk/wasi-sysroot -arbitrator_wasm_lib_flags_nogo=$(patsubst %, -l %, $(arbitrator_wasm_libs_nogo)) arbitrator_wasm_lib_flags=$(patsubst %, -l %, $(arbitrator_wasm_libs)) -rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/*.toml) +rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/src/*/*.* arbitrator/arbutil/*.toml arbitrator/caller-env/src/*.* arbitrator/caller-env/src/*/*.* arbitrator/caller-env/*.toml) .make/cbrotli-lib -prover_src = arbitrator/prover/src -rust_prover_files = $(wildcard $(prover_src)/*.* $(prover_src)/*/*.* arbitrator/prover/*.toml) $(rust_arbutil_files) +prover_direct_includes = $(patsubst %,$(output_latest)/%.wasm, forward forward_stub) +prover_dir = arbitrator/prover/ +rust_prover_files = $(wildcard $(prover_dir)/src/*.* $(prover_dir)/src/*/*.* $(prover_dir)/*.toml $(prover_dir)/*.rs) $(rust_arbutil_files) $(prover_direct_includes) $(arb_brotli_files) -jit_dir = arbitrator/jit -jit_files = $(wildcard $(jit_dir)/*.toml $(jit_dir)/*.rs $(jit_dir)/src/*.rs) $(rust_arbutil_files) +wasm_lib = arbitrator/wasm-libraries +wasm_lib_deps = $(wildcard $(wasm_lib)/$(1)/*.toml $(wasm_lib)/$(1)/src/*.rs $(wasm_lib)/$(1)/*.rs) $(rust_arbutil_files) $(arb_brotli_files) .make/machines +wasm_lib_go_abi = $(call wasm_lib_deps,go-abi) +wasm_lib_forward = $(call wasm_lib_deps,forward) +wasm_lib_user_host_trait = $(call wasm_lib_deps,user-host-trait) +wasm_lib_user_host = $(call wasm_lib_deps,user-host) $(wasm_lib_user_host_trait) + +forward_dir = $(wasm_lib)/forward + +stylus_files = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(wasm_lib_user_host_trait) $(rust_prover_files) -arbitrator_wasm_wasistub_files = $(wildcard arbitrator/wasm-libraries/wasi-stub/src/*/*) -arbitrator_wasm_gostub_files = $(wildcard arbitrator/wasm-libraries/go-stub/src/*/*) -arbitrator_wasm_hostio_files = $(wildcard arbitrator/wasm-libraries/host-io/src/*/*) +jit_dir = arbitrator/jit +jit_files = $(wildcard $(jit_dir)/*.toml $(jit_dir)/*.rs $(jit_dir)/src/*.rs $(jit_dir)/src/*/*.rs) $(stylus_files) + +wasm32_wasi = target/wasm32-wasi/release +wasm32_unknown = target/wasm32-unknown-unknown/release + +stylus_dir = arbitrator/stylus +stylus_test_dir = arbitrator/stylus/tests +stylus_cargo = arbitrator/stylus/tests/.cargo/config.toml + +rust_sdk = arbitrator/langs/rust +c_sdk = arbitrator/langs/c +stylus_lang_rust = $(wildcard $(rust_sdk)/*/src/*.rs $(rust_sdk)/*/src/*/*.rs $(rust_sdk)/*/*.toml) +stylus_lang_c = $(wildcard $(c_sdk)/*/*.c $(c_sdk)/*/*.h) +stylus_lang_bf = $(wildcard arbitrator/langs/bf/src/*.* arbitrator/langs/bf/src/*.toml) + +STYLUS_NIGHTLY_VER ?= "+nightly" + +cargo_nightly = cargo $(STYLUS_NIGHTLY_VER) build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort + +get_stylus_test_wasm = $(stylus_test_dir)/$(1)/$(wasm32_unknown)/$(1).wasm +get_stylus_test_rust = $(wildcard $(stylus_test_dir)/$(1)/*.toml $(stylus_test_dir)/$(1)/src/*.rs) $(stylus_cargo) $(stylus_lang_rust) +get_stylus_test_c = $(wildcard $(c_sdk)/examples/$(1)/*.c $(c_sdk)/examples/$(1)/*.h) $(stylus_lang_c) +stylus_test_bfs = $(wildcard $(stylus_test_dir)/bf/*.b) + +stylus_test_keccak_wasm = $(call get_stylus_test_wasm,keccak) +stylus_test_keccak_src = $(call get_stylus_test_rust,keccak) +stylus_test_keccak-100_wasm = $(call get_stylus_test_wasm,keccak-100) +stylus_test_keccak-100_src = $(call get_stylus_test_rust,keccak-100) +stylus_test_fallible_wasm = $(call get_stylus_test_wasm,fallible) +stylus_test_fallible_src = $(call get_stylus_test_rust,fallible) +stylus_test_storage_wasm = $(call get_stylus_test_wasm,storage) +stylus_test_storage_src = $(call get_stylus_test_rust,storage) +stylus_test_multicall_wasm = $(call get_stylus_test_wasm,multicall) +stylus_test_multicall_src = $(call get_stylus_test_rust,multicall) +stylus_test_log_wasm = $(call get_stylus_test_wasm,log) +stylus_test_log_src = $(call get_stylus_test_rust,log) +stylus_test_create_wasm = $(call get_stylus_test_wasm,create) +stylus_test_create_src = $(call get_stylus_test_rust,create) +stylus_test_math_wasm = $(call get_stylus_test_wasm,math) +stylus_test_math_src = $(call get_stylus_test_rust,math) +stylus_test_evm-data_wasm = $(call get_stylus_test_wasm,evm-data) +stylus_test_evm-data_src = $(call get_stylus_test_rust,evm-data) +stylus_test_sdk-storage_wasm = $(call get_stylus_test_wasm,sdk-storage) +stylus_test_sdk-storage_src = $(call get_stylus_test_rust,sdk-storage) +stylus_test_erc20_wasm = $(call get_stylus_test_wasm,erc20) +stylus_test_erc20_src = $(call get_stylus_test_rust,erc20) +stylus_test_read-return-data_wasm = $(call get_stylus_test_wasm,read-return-data) +stylus_test_read-return-data_src = $(call get_stylus_test_rust,read-return-data) + +stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_math_wasm) $(stylus_test_sdk-storage_wasm) $(stylus_test_erc20_wasm) $(stylus_test_read-return-data_wasm) $(stylus_test_evm-data_wasm) $(stylus_test_bfs:.b=.wasm) +stylus_benchmarks = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(stylus_test_wasms) # user targets @@ -95,17 +160,19 @@ build-node-deps: $(go_source) build-prover-header build-prover-lib build-jit .ma test-go-deps: \ build-replay-env \ + $(stylus_test_wasms) \ + $(arbitrator_stylus_lib) \ $(patsubst %,$(arbitrator_cases)/%.wasm, global-state read-inboxmsg-10 global-state-wrapper const) build-prover-header: $(arbitrator_generated_header) -build-prover-lib: $(arbitrator_prover_lib) +build-prover-lib: $(arbitrator_stylus_lib) -build-prover-bin: $(arbitrator_prover_bin) +build-prover-bin: $(prover_bin) build-jit: $(arbitrator_jit) -build-replay-env: $(arbitrator_prover_bin) $(arbitrator_jit) $(arbitrator_wasm_libs) $(replay_wasm) $(output_root)/machines/latest/machine.wavm.br +build-replay-env: $(prover_bin) $(arbitrator_jit) $(arbitrator_wasm_libs) $(replay_wasm) $(output_latest)/machine.wavm.br build-wasm-libs: $(arbitrator_wasm_libs) @@ -122,6 +189,10 @@ format fmt: .make/fmt lint: .make/lint @printf $(done) +stylus-benchmarks: $(stylus_benchmarks) + cargo test --manifest-path $< --release --features benchmark benchmark_ -- --nocapture + @printf $(done) + test-go: .make/test-go @printf $(done) @@ -129,22 +200,27 @@ test-go-challenge: test-go-deps go test -v -timeout 120m ./system_tests/... -run TestChallenge -tags challengetest @printf $(done) +test-go-stylus: test-go-deps + go test -v -timeout 120m ./system_tests/... -run TestProgramArbitrator -tags stylustest + @printf $(done) + test-go-redis: test-go-deps TEST_REDIS=redis://localhost:6379/0 go test -p 1 -run TestRedis ./system_tests/... ./arbnode/... @printf $(done) test-gen-proofs: \ + $(arbitrator_test_wasms) \ $(patsubst $(arbitrator_cases)/%.wat,contracts/test/prover/proofs/%.json, $(arbitrator_tests_wat)) \ $(patsubst $(arbitrator_cases)/rust/src/bin/%.rs,contracts/test/prover/proofs/rust-%.json, $(arbitrator_tests_rust)) \ contracts/test/prover/proofs/go.json -wasm-ci-build: $(arbitrator_wasm_libs) $(arbitrator_test_wasms) +wasm-ci-build: $(arbitrator_wasm_libs) $(arbitrator_test_wasms) $(stylus_test_wasms) $(output_latest)/user_test.wasm @printf $(done) clean: go clean -testcache rm -rf $(arbitrator_cases)/rust/target - rm -f $(arbitrator_cases)/*.wasm $(arbitrator_cases)/go/main + rm -f $(arbitrator_cases)/*.wasm $(arbitrator_cases)/go/testcase.wasm rm -rf arbitrator/wasm-testsuite/tests rm -rf $(output_root) rm -f contracts/test/prover/proofs/*.json contracts/test/prover/spec-proofs/*.json @@ -154,6 +230,8 @@ clean: rm -f arbitrator/wasm-libraries/soft-float/*.o rm -f arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/*.o rm -f arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/*.a + rm -f arbitrator/wasm-libraries/forward/*.wat + rm -rf arbitrator/stylus/tests/*/target/ arbitrator/stylus/tests/*/*.wasm @rm -rf contracts/build contracts/cache solgen/go/ @rm -f .make/* @@ -191,39 +269,38 @@ $(output_root)/bin/seq-coordinator-manager: $(DEP_PREDICATE) build-node-deps # recompile wasm, but don't change timestamp unless files differ $(replay_wasm): $(DEP_PREDICATE) $(go_source) .make/solgen mkdir -p `dirname $(replay_wasm)` - GOOS=js GOARCH=wasm go build -o $(output_root)/tmp/replay.wasm ./cmd/replay/... - if ! diff -qN $(output_root)/tmp/replay.wasm $@ > /dev/null; then cp $(output_root)/tmp/replay.wasm $@; fi + GOOS=wasip1 GOARCH=wasm go build -o $@ ./cmd/replay/... -$(arbitrator_prover_bin): $(DEP_PREDICATE) $(rust_prover_files) - mkdir -p `dirname $(arbitrator_prover_bin)` +$(prover_bin): $(DEP_PREDICATE) $(rust_prover_files) + mkdir -p `dirname $(prover_bin)` cargo build --manifest-path arbitrator/Cargo.toml --release --bin prover ${CARGOFLAGS} install arbitrator/target/release/prover $@ -$(arbitrator_prover_lib): $(DEP_PREDICATE) $(rust_prover_files) - mkdir -p `dirname $(arbitrator_prover_lib)` - cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p prover ${CARGOFLAGS} - install arbitrator/target/release/libprover.a $@ +$(arbitrator_stylus_lib): $(DEP_PREDICATE) $(stylus_files) + mkdir -p `dirname $(arbitrator_stylus_lib)` + cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p stylus ${CARGOFLAGS} + install arbitrator/target/release/libstylus.a $@ -$(arbitrator_jit): $(DEP_PREDICATE) .make/cbrotli-lib $(jit_files) +$(arbitrator_jit): $(DEP_PREDICATE) $(jit_files) mkdir -p `dirname $(arbitrator_jit)` - cargo build --manifest-path arbitrator/Cargo.toml --release --bin jit ${CARGOFLAGS} + cargo build --manifest-path arbitrator/Cargo.toml --release -p jit ${CARGOFLAGS} install arbitrator/target/release/jit $@ -$(arbitrator_cases)/rust/target/wasm32-wasi/release/%.wasm: $(arbitrator_cases)/rust/src/bin/%.rs $(arbitrator_cases)/rust/src/lib.rs - cargo build --manifest-path $(arbitrator_cases)/rust/Cargo.toml --release --target wasm32-wasi --bin $(patsubst $(arbitrator_cases)/rust/target/wasm32-wasi/release/%.wasm,%, $@) +$(arbitrator_cases)/rust/$(wasm32_wasi)/%.wasm: $(arbitrator_cases)/rust/src/bin/%.rs $(arbitrator_cases)/rust/src/lib.rs + cargo build --manifest-path $(arbitrator_cases)/rust/Cargo.toml --release --target wasm32-wasi --bin $(patsubst $(arbitrator_cases)/rust/$(wasm32_wasi)/%.wasm,%, $@) -$(arbitrator_cases)/go/main: $(arbitrator_cases)/go/main.go - cd $(arbitrator_cases)/go && GOOS=js GOARCH=wasm go build main.go +$(arbitrator_cases)/go/testcase.wasm: $(arbitrator_cases)/go/*.go .make/solgen + cd $(arbitrator_cases)/go && GOOS=wasip1 GOARCH=wasm go build -o testcase.wasm -$(arbitrator_generated_header): $(DEP_PREDICATE) arbitrator/prover/src/lib.rs arbitrator/prover/src/utils.rs +$(arbitrator_generated_header): $(DEP_PREDICATE) $(stylus_files) @echo creating ${PWD}/$(arbitrator_generated_header) mkdir -p `dirname $(arbitrator_generated_header)` - cd arbitrator && cbindgen --config cbindgen.toml --crate prover --output ../$(arbitrator_generated_header) + cd arbitrator/stylus && cbindgen --config cbindgen.toml --crate stylus --output ../../$(arbitrator_generated_header) + @touch -c $@ # cargo might decide to not rebuild the header -$(output_root)/machines/latest/wasi_stub.wasm: $(DEP_PREDICATE) $(arbitrator_wasm_wasistub_files) - mkdir -p $(output_root)/machines/latest +$(output_latest)/wasi_stub.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,wasi-stub) cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-unknown-unknown --package wasi-stub - install arbitrator/wasm-libraries/target/wasm32-unknown-unknown/release/wasi_stub.wasm $@ + install arbitrator/wasm-libraries/$(wasm32_unknown)/wasi_stub.wasm $@ arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/softfloat.a: $(DEP_PREDICATE) \ arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/Makefile \ @@ -240,12 +317,11 @@ arbitrator/wasm-libraries/soft-float/bindings32.o: $(DEP_PREDICATE) arbitrator/w arbitrator/wasm-libraries/soft-float/bindings64.o: $(DEP_PREDICATE) arbitrator/wasm-libraries/soft-float/bindings64.c clang arbitrator/wasm-libraries/soft-float/bindings64.c --sysroot $(WASI_SYSROOT) -I arbitrator/wasm-libraries/soft-float/SoftFloat/source/include -target wasm32-wasi -Wconversion -c -o $@ -$(output_root)/machines/latest/soft-float.wasm: $(DEP_PREDICATE) \ +$(output_latest)/soft-float.wasm: $(DEP_PREDICATE) \ arbitrator/wasm-libraries/soft-float/bindings32.o \ arbitrator/wasm-libraries/soft-float/bindings64.o \ arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/softfloat.a \ - .make/wasm-lib - mkdir -p $(output_root)/machines/latest + .make/wasm-lib .make/machines wasm-ld \ arbitrator/wasm-libraries/soft-float/bindings32.o \ arbitrator/wasm-libraries/soft-float/bindings64.o \ @@ -264,49 +340,133 @@ $(output_root)/machines/latest/soft-float.wasm: $(DEP_PREDICATE) \ --export wavm__f32_demote_f64 \ --export wavm__f64_promote_f32 -$(output_root)/machines/latest/go_stub.wasm: $(DEP_PREDICATE) $(wildcard arbitrator/wasm-libraries/go-stub/src/*) - mkdir -p $(output_root)/machines/latest - cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package go-stub - install arbitrator/wasm-libraries/target/wasm32-wasi/release/go_stub.wasm $@ - -$(output_root)/machines/latest/host_io.wasm: $(DEP_PREDICATE) $(wildcard arbitrator/wasm-libraries/host-io/src/*) - mkdir -p $(output_root)/machines/latest +$(output_latest)/host_io.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,host-io) $(wasm_lib_go_abi) cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package host-io - install arbitrator/wasm-libraries/target/wasm32-wasi/release/host_io.wasm $@ + install arbitrator/wasm-libraries/$(wasm32_wasi)/host_io.wasm $@ + +$(output_latest)/user_host.wasm: $(DEP_PREDICATE) $(wasm_lib_user_host) $(rust_prover_files) $(output_latest)/forward_stub.wasm .make/machines + cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package user-host + install arbitrator/wasm-libraries/$(wasm32_wasi)/user_host.wasm $@ + +$(output_latest)/program_exec.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,program-exec) $(rust_prover_files) .make/machines + cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package program-exec + install arbitrator/wasm-libraries/$(wasm32_wasi)/program_exec.wasm $@ + +$(output_latest)/user_test.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,user-test) $(rust_prover_files) .make/machines + cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package user-test + install arbitrator/wasm-libraries/$(wasm32_wasi)/user_test.wasm $@ + +$(output_latest)/arbcompress.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,brotli) $(wasm_lib_go_abi) + cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package arbcompress + install arbitrator/wasm-libraries/$(wasm32_wasi)/arbcompress.wasm $@ -$(output_root)/machines/latest/brotli.wasm: $(DEP_PREDICATE) $(wildcard arbitrator/wasm-libraries/brotli/src/*) .make/cbrotli-wasm - mkdir -p $(output_root)/machines/latest - cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package brotli - install arbitrator/wasm-libraries/target/wasm32-wasi/release/brotli.wasm $@ +$(output_latest)/forward.wasm: $(DEP_PREDICATE) $(wasm_lib_forward) .make/machines + cargo run --manifest-path $(forward_dir)/Cargo.toml -- --path $(forward_dir)/forward.wat + wat2wasm $(wasm_lib)/forward/forward.wat -o $@ -$(output_root)/machines/latest/machine.wavm.br: $(DEP_PREDICATE) $(arbitrator_prover_bin) $(arbitrator_wasm_libs) $(replay_wasm) - $(arbitrator_prover_bin) $(replay_wasm) --generate-binaries $(output_root)/machines/latest -l $(output_root)/machines/latest/soft-float.wasm -l $(output_root)/machines/latest/wasi_stub.wasm -l $(output_root)/machines/latest/go_stub.wasm -l $(output_root)/machines/latest/host_io.wasm -l $(output_root)/machines/latest/brotli.wasm +$(output_latest)/forward_stub.wasm: $(DEP_PREDICATE) $(wasm_lib_forward) .make/machines + cargo run --manifest-path $(forward_dir)/Cargo.toml -- --path $(forward_dir)/forward_stub.wat --stub + wat2wasm $(wasm_lib)/forward/forward_stub.wat -o $@ + +$(output_latest)/machine.wavm.br: $(DEP_PREDICATE) $(prover_bin) $(arbitrator_wasm_libs) $(replay_wasm) + $(prover_bin) $(replay_wasm) --generate-binaries $(output_latest) \ + $(patsubst %,-l $(output_latest)/%.wasm, forward soft-float wasi_stub host_io user_host arbcompress program_exec) $(arbitrator_cases)/%.wasm: $(arbitrator_cases)/%.wat wat2wasm $< -o $@ -contracts/test/prover/proofs/float%.json: $(arbitrator_cases)/float%.wasm $(arbitrator_prover_bin) $(output_root)/machines/latest/soft-float.wasm - $(arbitrator_prover_bin) $< -l $(output_root)/machines/latest/soft-float.wasm -o $@ -b --allow-hostapi --require-success --always-merkleize +$(stylus_test_dir)/%.wasm: $(stylus_test_dir)/%.b $(stylus_lang_bf) + cargo run --manifest-path arbitrator/langs/bf/Cargo.toml $< -o $@ + +$(stylus_test_keccak_wasm): $(stylus_test_keccak_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_keccak-100_wasm): $(stylus_test_keccak-100_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_fallible_wasm): $(stylus_test_fallible_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary -contracts/test/prover/proofs/no-stack-pollution.json: $(arbitrator_cases)/no-stack-pollution.wasm $(arbitrator_prover_bin) - $(arbitrator_prover_bin) $< -o $@ --allow-hostapi --require-success --always-merkleize +$(stylus_test_storage_wasm): $(stylus_test_storage_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_multicall_wasm): $(stylus_test_multicall_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_log_wasm): $(stylus_test_log_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_create_wasm): $(stylus_test_create_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_math_wasm): $(stylus_test_math_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_evm-data_wasm): $(stylus_test_evm-data_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_read-return-data_wasm): $(stylus_test_read-return-data_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_sdk-storage_wasm): $(stylus_test_sdk-storage_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +$(stylus_test_erc20_wasm): $(stylus_test_erc20_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + +contracts/test/prover/proofs/float%.json: $(arbitrator_cases)/float%.wasm $(prover_bin) $(output_latest)/soft-float.wasm + $(prover_bin) $< -l $(output_latest)/soft-float.wasm -o $@ -b --allow-hostapi --require-success --always-merkleize + +contracts/test/prover/proofs/no-stack-pollution.json: $(arbitrator_cases)/no-stack-pollution.wasm $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi --require-success --always-merkleize target/testdata/preimages.bin: mkdir -p `dirname $@` python3 scripts/create-test-preimages.py $@ -contracts/test/prover/proofs/rust-%.json: $(arbitrator_cases)/rust/target/wasm32-wasi/release/%.wasm $(arbitrator_prover_bin) $(arbitrator_wasm_libs_nogo) target/testdata/preimages.bin - $(arbitrator_prover_bin) $< $(arbitrator_wasm_lib_flags_nogo) -o $@ -b --allow-hostapi --require-success --inbox-add-stub-headers --inbox $(arbitrator_cases)/rust/data/msg0.bin --inbox $(arbitrator_cases)/rust/data/msg1.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg0.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg1.bin --preimages target/testdata/preimages.bin +contracts/test/prover/proofs/rust-%.json: $(arbitrator_cases)/rust/$(wasm32_wasi)/%.wasm $(prover_bin) $(arbitrator_wasm_libs) target/testdata/preimages.bin + $(prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -b --allow-hostapi --require-success --inbox-add-stub-headers --inbox $(arbitrator_cases)/rust/data/msg0.bin --inbox $(arbitrator_cases)/rust/data/msg1.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg0.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg1.bin --preimages target/testdata/preimages.bin -contracts/test/prover/proofs/go.json: $(arbitrator_cases)/go/main $(arbitrator_prover_bin) $(arbitrator_wasm_libs) target/testdata/preimages.bin - $(arbitrator_prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -i 5000000 --require-success --preimages target/testdata/preimages.bin +contracts/test/prover/proofs/go.json: $(arbitrator_cases)/go/testcase.wasm $(prover_bin) $(arbitrator_wasm_libs) target/testdata/preimages.bin $(arbitrator_tests_link_deps) $(arbitrator_cases)/user.wasm + $(prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -b --require-success --preimages target/testdata/preimages.bin --stylus-modules $(arbitrator_cases)/user.wasm + +# avoid testing user.wasm in onestepproofs. It can only run as stylus program. +contracts/test/prover/proofs/user.json: + echo "[]" > $@ # avoid testing read-inboxmsg-10 in onestepproofs. It's used for go challenge testing. contracts/test/prover/proofs/read-inboxmsg-10.json: echo "[]" > $@ -contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(arbitrator_prover_bin) - $(arbitrator_prover_bin) $< -o $@ --allow-hostapi --always-merkleize +contracts/test/prover/proofs/global-state.json: + echo "[]" > $@ + +contracts/test/prover/proofs/forward-test.json: $(arbitrator_cases)/forward-test.wasm $(arbitrator_tests_forward_deps) $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi --always-merkleize $(patsubst %,-l %, $(arbitrator_tests_forward_deps)) + +contracts/test/prover/proofs/link.json: $(arbitrator_cases)/link.wasm $(arbitrator_tests_link_deps) $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi --always-merkleize --stylus-modules $(arbitrator_tests_link_deps) --require-success + +contracts/test/prover/proofs/dynamic.json: $(patsubst %,$(arbitrator_cases)/%.wasm, dynamic user) $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi --always-merkleize --stylus-modules $(arbitrator_cases)/user.wasm --require-success + +contracts/test/prover/proofs/bulk-memory.json: $(patsubst %,$(arbitrator_cases)/%.wasm, bulk-memory) $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi --always-merkleize --stylus-modules $(arbitrator_cases)/user.wasm -b + +contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi --always-merkleize # strategic rules to minimize dependency building @@ -318,13 +478,14 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(arbitrator_pro .make/fmt: $(DEP_PREDICATE) build-node-deps .make/yarndeps $(ORDER_ONLY_PREDICATE) .make golangci-lint run --disable-all -E gofmt --fix - cargo fmt --all --manifest-path arbitrator/Cargo.toml -- --check + cargo fmt -p arbutil -p prover -p jit -p stylus --manifest-path arbitrator/Cargo.toml -- --check cargo fmt --all --manifest-path arbitrator/wasm-testsuite/Cargo.toml -- --check + cargo fmt --all --manifest-path arbitrator/langs/rust/Cargo.toml -- --check yarn --cwd contracts prettier:solidity @touch $@ .make/test-go: $(DEP_PREDICATE) $(go_source) build-node-deps test-go-deps $(ORDER_ONLY_PREDICATE) .make - gotestsum --format short-verbose + gotestsum --format short-verbose --no-color=false @touch $@ .make/solgen: $(DEP_PREDICATE) solgen/gen.go .make/solidity $(ORDER_ONLY_PREDICATE) .make @@ -360,6 +521,10 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(arbitrator_pro test -f arbitrator/wasm-libraries/soft-float/bindings64.o || ./scripts/build-brotli.sh -f -d -t .. @touch $@ +.make/machines: $(DEP_PREDICATE) $(ORDER_ONLY_PREDICATE) .make + mkdir -p $(output_latest) + touch $@ + .make: mkdir .make @@ -368,4 +533,4 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(arbitrator_pro always: # use this to force other rules to always build .DELETE_ON_ERROR: # causes a failure to delete its target -.PHONY: push all build build-node-deps test-go-deps build-prover-header build-prover-lib build-prover-bin build-jit build-replay-env build-solidity build-wasm-libs contracts format fmt lint test-go test-gen-proofs push clean docker +.PHONY: push all build build-node-deps test-go-deps build-prover-header build-prover-lib build-prover-bin build-jit build-replay-env build-solidity build-wasm-libs contracts format fmt lint stylus-benchmarks test-go test-gen-proofs push clean docker diff --git a/README.md b/README.md index 0e463f50a..8f262c365 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Arbitrum One successfully migrated from the Classic Arbitrum stack onto Nitro on ## License -Nitro is currently licensed under a [Business Source License](./LICENSE), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains. +Nitro is currently licensed under a [Business Source License](./LICENSE.md), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains. The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova. diff --git a/arbcompress/compress_cgo.go b/arbcompress/compress_cgo.go deleted file mode 100644 index 47da42941..000000000 --- a/arbcompress/compress_cgo.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -//go:build !js -// +build !js - -package arbcompress - -/* -#cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/ -#cgo LDFLAGS: ${SRCDIR}/../target/lib/libbrotlidec-static.a ${SRCDIR}/../target/lib/libbrotlienc-static.a ${SRCDIR}/../target/lib/libbrotlicommon-static.a -lm -#include "brotli/encode.h" -#include "brotli/decode.h" -*/ -import "C" -import ( - "fmt" -) - -func Decompress(input []byte, maxSize int) ([]byte, error) { - outbuf := make([]byte, maxSize) - outsize := C.size_t(maxSize) - var ptr *C.uint8_t - if len(input) > 0 { - ptr = (*C.uint8_t)(&input[0]) - } - res := C.BrotliDecoderDecompress(C.size_t(len(input)), ptr, &outsize, (*C.uint8_t)(&outbuf[0])) - if res != 1 { - return nil, fmt.Errorf("failed decompression: %d", res) - } - if int(outsize) > maxSize { - return nil, fmt.Errorf("result too large: %d", outsize) - } - return outbuf[:outsize], nil -} - -func compressLevel(input []byte, level int) ([]byte, error) { - maxOutSize := compressedBufferSizeFor(len(input)) - outbuf := make([]byte, maxOutSize) - outSize := C.size_t(maxOutSize) - var inputPtr *C.uint8_t - if len(input) > 0 { - inputPtr = (*C.uint8_t)(&input[0]) - } - res := C.BrotliEncoderCompress(C.int(level), C.BROTLI_DEFAULT_WINDOW, C.BROTLI_MODE_GENERIC, - C.size_t(len(input)), inputPtr, &outSize, (*C.uint8_t)(&outbuf[0])) - if res != 1 { - return nil, fmt.Errorf("failed compression: %d", res) - } - return outbuf[:outSize], nil -} - -func CompressWell(input []byte) ([]byte, error) { - return compressLevel(input, LEVEL_WELL) -} diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go index 990fd2e2b..a61dd9a17 100644 --- a/arbcompress/compress_common.go +++ b/arbcompress/compress_common.go @@ -1,8 +1,15 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbcompress +type Dictionary uint32 + +const ( + EmptyDictionary Dictionary = iota + StylusProgramDictionary +) + const LEVEL_WELL = 11 const WINDOW_SIZE = 22 // BROTLI_DEFAULT_WINDOW @@ -11,5 +18,5 @@ func compressedBufferSizeFor(length int) int { } func CompressLevel(input []byte, level int) ([]byte, error) { - return compressLevel(input, level) + return Compress(input, uint32(level), EmptyDictionary) } diff --git a/arbcompress/compress_wasm.go b/arbcompress/compress_wasm.go deleted file mode 100644 index ba2eb1d10..000000000 --- a/arbcompress/compress_wasm.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -//go:build js -// +build js - -package arbcompress - -import ( - "fmt" -) - -func brotliCompress(inBuf []byte, outBuf []byte, level int, windowSize int) int64 - -func brotliDecompress(inBuf []byte, outBuf []byte) int64 - -func Decompress(input []byte, maxSize int) ([]byte, error) { - outBuf := make([]byte, maxSize) - outLen := brotliDecompress(input, outBuf) - if outLen < 0 { - return nil, fmt.Errorf("failed decompression") - } - return outBuf[:outLen], nil -} - -func compressLevel(input []byte, level int) ([]byte, error) { - maxOutSize := compressedBufferSizeFor(len(input)) - outBuf := make([]byte, maxOutSize) - outLen := brotliCompress(input, outBuf, level, WINDOW_SIZE) - if outLen < 0 { - return nil, fmt.Errorf("failed compression") - } - return outBuf[:outLen], nil -} diff --git a/arbcompress/native.go b/arbcompress/native.go new file mode 100644 index 000000000..4624d6222 --- /dev/null +++ b/arbcompress/native.go @@ -0,0 +1,76 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + +package arbcompress + +/* +#cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/ +#cgo LDFLAGS: ${SRCDIR}/../target/lib/libstylus.a -lm +#include "arbitrator.h" +*/ +import "C" +import "fmt" + +type u8 = C.uint8_t +type u32 = C.uint32_t +type usize = C.size_t + +type brotliBool = uint32 +type brotliBuffer = C.BrotliBuffer + +const ( + brotliFalse brotliBool = iota + brotliTrue +) + +func CompressWell(input []byte) ([]byte, error) { + return Compress(input, LEVEL_WELL, EmptyDictionary) +} + +func Compress(input []byte, level uint32, dictionary Dictionary) ([]byte, error) { + maxSize := compressedBufferSizeFor(len(input)) + output := make([]byte, maxSize) + outbuf := sliceToBuffer(output) + inbuf := sliceToBuffer(input) + + status := C.brotli_compress(inbuf, outbuf, C.Dictionary(dictionary), u32(level)) + if status != C.BrotliStatus_Success { + return nil, fmt.Errorf("failed decompression: %d", status) + } + output = output[:*outbuf.len] + return output, nil +} + +func Decompress(input []byte, maxSize int) ([]byte, error) { + return DecompressWithDictionary(input, maxSize, EmptyDictionary) +} + +func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { + output := make([]byte, maxSize) + outbuf := sliceToBuffer(output) + inbuf := sliceToBuffer(input) + + status := C.brotli_decompress(inbuf, outbuf, C.Dictionary(dictionary)) + if status != C.BrotliStatus_Success { + return nil, fmt.Errorf("failed decompression: %d", status) + } + if *outbuf.len > usize(maxSize) { + return nil, fmt.Errorf("failed decompression: result too large: %d", *outbuf.len) + } + output = output[:*outbuf.len] + return output, nil +} + +func sliceToBuffer(slice []byte) brotliBuffer { + count := usize(len(slice)) + if count == 0 { + slice = []byte{0x00} // ensures pointer is not null (shouldn't be necessary, but brotli docs are picky about NULL) + } + return brotliBuffer{ + ptr: (*u8)(&slice[0]), + len: &count, + } +} diff --git a/arbcompress/raw.s b/arbcompress/raw.s deleted file mode 100644 index 5e4b053b9..000000000 --- a/arbcompress/raw.s +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright 2021, Offchain Labs, Inc. All rights reserved. -// - -//go:build js -// +build js - -#include "textflag.h" - -TEXT ·brotliCompress(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·brotliDecompress(SB), NOSPLIT, $0 - CallImport - RET diff --git a/arbcompress/wasm.go b/arbcompress/wasm.go new file mode 100644 index 000000000..71d704ce0 --- /dev/null +++ b/arbcompress/wasm.go @@ -0,0 +1,64 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +//go:build wasm +// +build wasm + +package arbcompress + +import ( + "fmt" + "unsafe" + + "github.com/offchainlabs/nitro/arbutil" +) + +type brotliStatus = uint32 + +const ( + brotliFailure brotliStatus = iota + brotliSuccess +) + +//go:wasmimport arbcompress brotli_compress +func brotliCompress(inBuf unsafe.Pointer, inLen uint32, outBuf unsafe.Pointer, outLen unsafe.Pointer, level, windowSize uint32, dictionary Dictionary) brotliStatus + +//go:wasmimport arbcompress brotli_decompress +func brotliDecompress(inBuf unsafe.Pointer, inLen uint32, outBuf unsafe.Pointer, outLen unsafe.Pointer, dictionary Dictionary) brotliStatus + +func Compress(input []byte, level uint32, dictionary Dictionary) ([]byte, error) { + maxOutSize := compressedBufferSizeFor(len(input)) + outBuf := make([]byte, maxOutSize) + outLen := uint32(len(outBuf)) + status := brotliCompress( + arbutil.SliceToUnsafePointer(input), uint32(len(input)), + arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), + uint32(level), + WINDOW_SIZE, + dictionary, + ) + if status != brotliSuccess { + return nil, fmt.Errorf("failed compression") + } + return outBuf[:outLen], nil +} + +func Decompress(input []byte, maxSize int) ([]byte, error) { + return DecompressWithDictionary(input, maxSize, EmptyDictionary) +} + +func DecompressWithDictionary(input []byte, maxSize int, dictionary Dictionary) ([]byte, error) { + outBuf := make([]byte, maxSize) + outLen := uint32(len(outBuf)) + status := brotliDecompress( + arbutil.SliceToUnsafePointer(input), + uint32(len(input)), + arbutil.SliceToUnsafePointer(outBuf), + unsafe.Pointer(&outLen), + dictionary, + ) + if status != brotliSuccess { + return nil, fmt.Errorf("failed decompression") + } + return outBuf[:outLen], nil +} diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index a3b0c5e64..350c83e4b 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -4,11 +4,11 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ - "gimli", + "gimli 0.29.0", ] [[package]] @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -30,11 +30,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "version_check", "zerocopy", @@ -42,18 +42,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] -name = "aliasable" -version = "0.1.3" +name = "allocator-api2" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anes" @@ -63,9 +63,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] @@ -76,14 +76,29 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arbutil" version = "0.1.0" dependencies = [ "digest 0.10.7", + "eyre", + "fnv", + "hex", + "num-traits", "num_enum", + "ruint2", + "serde", "sha2 0.10.8", "sha3 0.10.8", + "siphasher", + "tiny-keccak", + "wasmparser", ] [[package]] @@ -141,7 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] @@ -154,7 +169,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] @@ -190,7 +205,7 @@ checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] @@ -205,9 +220,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "atty" @@ -222,22 +237,22 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", - "miniz_oxide 0.5.3", - "object 0.29.0", + "miniz_oxide", + "object 0.35.0", "rustc-demangle", ] @@ -256,29 +271,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.66.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" -dependencies = [ - "bitflags 2.4.1", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.45", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -287,9 +279,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "block-buffer" @@ -329,71 +333,70 @@ dependencies = [ ] [[package]] -name = "brotli-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +name = "brotli" +version = "0.1.0" dependencies = [ - "cc", - "libc", + "lazy_static", + "num_enum", + "wasmer", + "wee_alloc", ] [[package]] -name = "brotli2" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +name = "brotli-fuzz" +version = "0.0.0" dependencies = [ - "brotli-sys", - "libc", + "brotli", + "hex", + "libfuzzer-sys", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", + "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.3.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "c-kzg" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32700dc7904064bb64e857d38a1766607372928e2466ee5f02a869829b3297d7" +checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" dependencies = [ - "bindgen", "blst", "cc", "glob", @@ -402,6 +405,17 @@ dependencies = [ "serde", ] +[[package]] +name = "caller-env" +version = "0.1.0" +dependencies = [ + "brotli", + "num_enum", + "rand", + "rand_pcg", + "wasmer", +] + [[package]] name = "cast" version = "0.3.0" @@ -410,21 +424,20 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ + "jobserver", "libc", + "once_cell", ] [[package]] -name = "cexpr" -version = "0.6.0" +name = "cfg-if" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" @@ -459,22 +472,11 @@ dependencies = [ "half", ] -[[package]] -name = "clang-sys" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", @@ -487,18 +489,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstyle", "clap_lex", @@ -506,18 +508,24 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "corosensei" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" +checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.0", "libc", "scopeguard", "windows-sys 0.33.0", @@ -525,34 +533,37 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "cranelift-bforest" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529ffacce2249ac60edba2941672dfedf3d96558b415d0d8083cd007456e0f55" +checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427d105f617efc8cb55f8d036a7fded2e227892d8780b4985e5551f8d27c4a92" +checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" dependencies = [ + "arrayvec", + "bumpalo", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", + "cranelift-egraph", "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.26.2", "log", "regalloc2", "smallvec", @@ -561,30 +572,44 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551674bed85b838d45358e3eab4f0ffaa6790c70dc08184204b9a54b41cdb7d1" +checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b3a63ae57498c3eb495360944a33571754241e15e47e3bcae6082f40fec5866" +checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" + +[[package]] +name = "cranelift-egraph" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" +dependencies = [ + "cranelift-entity", + "fxhash", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "log", + "smallvec", +] [[package]] name = "cranelift-entity" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11aa8aa624c72cc1c94ea3d0739fa61248260b5b14d3646f51593a88d67f3e6e" +checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" [[package]] name = "cranelift-frontend" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "544ee8f4d1c9559c9aa6d46e7aaeac4a13856d620561094f35527356c7d21bd0" +checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" dependencies = [ "cranelift-codegen", "log", @@ -594,9 +619,9 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.86.1" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed16b14363d929b8c37e3c557d0a7396791b383ecc302141643c054343170aad" +checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" [[package]] name = "crc32fast" @@ -604,7 +629,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -616,7 +641,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.4", + "clap 4.5.8", "criterion-plot", "is-terminal", "itertools", @@ -645,48 +670,47 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "cfg-if", "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", ] [[package]] -name = "crossbeam-utils" -version = "0.8.8" +name = "crossbeam-queue" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", - "lazy_static", + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crunchy" version = "0.2.2" @@ -709,8 +733,18 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core 0.20.9", + "darling_macro 0.20.9", ] [[package]] @@ -724,7 +758,20 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 1.0.76", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.66", ] [[package]] @@ -733,9 +780,33 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote", - "syn 1.0.76", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core 0.20.9", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -746,7 +817,20 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", ] [[package]] @@ -789,11 +873,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dynasm" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dynasmrt" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +dependencies = [ + "byteorder", + "dynasm", + "memmap2 0.5.10", +] + [[package]] name = "either" -version = "1.6.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "enum-iterator" @@ -812,28 +922,28 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] name = "enumset" -version = "1.0.11" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" dependencies = [ - "darling", + "darling 0.20.9", "proc-macro2", "quote", - "syn 1.0.76", + "syn 2.0.66", ] [[package]] @@ -842,21 +952,11 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "eyre" -version = "0.6.5" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -875,7 +975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", - "miniz_oxide 0.7.3", + "miniz_oxide", ] [[package]] @@ -893,6 +993,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "fxhash" version = "0.2.1" @@ -904,9 +1010,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -918,7 +1024,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -930,10 +1036,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", - "indexmap 1.8.1", + "indexmap 1.9.3", "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "glob" version = "0.3.1" @@ -946,23 +1058,17 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.8", ] [[package]] @@ -971,14 +1077,18 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] [[package]] name = "heck" @@ -989,12 +1099,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -1022,15 +1126,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1055,57 +1150,47 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] name = "indexmap" -version = "2.0.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.5", ] [[package]] name = "inkwell" -version = "0.1.0-beta.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2223d0eba0ae6d40a3e4680c6a3209143471e1f38b41746ea309aa36dde9f90b" +checksum = "bbac11e485159a525867fb7e6aa61981453e6a72f625fde6a4ab3047b0c6dec9" dependencies = [ "either", "inkwell_internals", "libc", "llvm-sys", "once_cell", - "parking_lot 0.11.2", - "regex", + "parking_lot", ] [[package]] name = "inkwell_internals" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7090af3d300424caa81976b8c97bca41cd70e861272c072e188ae082fb49f9" +checksum = "87d00c17e264ce02be5bc23d7bff959188ec7137beddd06b8b6b05a7c680ea85" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", + "syn 1.0.109", ] [[package]] @@ -1121,53 +1206,66 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jit" version = "0.1.0" dependencies = [ "arbutil", + "brotli", + "caller-env", "eyre", "hex", "libc", - "ouroboros", - "parking_lot 0.12.1", + "parking_lot", + "prover", "rand", "rand_pcg", + "sha2 0.9.9", "sha3 0.9.1", "structopt", + "stylus", "thiserror", "wasmer", "wasmer-compiler-cranelift", "wasmer-compiler-llvm", ] +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -1178,12 +1276,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "leb128" version = "0.2.5" @@ -1197,13 +1289,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] -name = "libloading" -version = "0.8.1" +name = "libfuzzer-sys" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "arbitrary", + "cc", + "once_cell", ] [[package]] @@ -1212,34 +1305,28 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" - [[package]] name = "llvm-sys" -version = "120.2.4" +version = "150.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b716322964966a62377cf86e64f00ca7043505fdf27bd2ec7d41ae6682d1e7" +checksum = "bfd60e740af945d99c2446a52e3ab8cdba2f740a40a16c51f6871bdea2abc687" dependencies = [ "cc", "lazy_static", "libc", "regex", - "semver 0.11.0", + "semver", ] [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1247,11 +1334,17 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "cfg-if", + "hashbrown 0.14.5", ] [[package]] @@ -1263,44 +1356,59 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" -version = "2.4.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] [[package]] name = "memmap2" -version = "0.5.7" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] -name = "minimal-lexical" -version = "0.1.3" +name = "memory_units" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] -name = "miniz_oxide" -version = "0.5.3" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" @@ -1319,13 +1427,12 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "nom" -version = "7.0.0" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -1351,9 +1458,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -1365,39 +1472,48 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1406,11 +1522,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -1418,42 +1533,42 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.45", + "syn 2.0.66", ] [[package]] @@ -1467,30 +1582,30 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "option-ext" @@ -1498,30 +1613,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ouroboros" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a6d0919a92ba28d8109a103e0de08f89706be0eeaad1130fd1a34030dee84a" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bc2307dc3420554ae349230dac4969c66d7c2feead3a8cab05ea0c604daca6" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.45", -] - [[package]] name = "overload" version = "0.1.1" @@ -1530,50 +1621,25 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.36.1", + "windows-targets 0.52.5", ] [[package]] @@ -1582,33 +1648,17 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" -dependencies = [ - "thiserror", - "ucd-trie", -] - [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "plotters" @@ -1644,23 +1694,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "prettyplease" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" -dependencies = [ - "proc-macro2", - "syn 2.0.45", -] - [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "once_cell", "toml_edit", ] @@ -1673,7 +1712,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", "version_check", ] @@ -1690,9 +1729,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -1708,18 +1747,25 @@ dependencies = [ "ark-serialize", "ark-std", "bincode", - "brotli2", + "brotli", "c-kzg", + "derivative", "digest 0.9.0", "eyre", "fnv", "hex", + "itertools", "lazy_static", "libc", + "lru", "nom", "nom-leb128", "num", "num-bigint", + "num-derive", + "num-traits", + "once_cell", + "parking_lot", "rayon", "rust-kzg-bn254", "rustc-demangle", @@ -1731,7 +1777,10 @@ dependencies = [ "smallvec", "static_assertions", "structopt", - "wasmparser 0.84.0", + "wasmer", + "wasmer-compiler-singlepass", + "wasmer-types", + "wasmparser", "wat", ] @@ -1752,18 +1801,24 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] name = "quote" -version = "1.0.34" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1787,9 +1842,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -1805,36 +1860,31 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", - "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] @@ -1850,9 +1900,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.3.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" +checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" dependencies = [ "fxhash", "log", @@ -1862,9 +1912,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -1873,27 +1935,27 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "region" -version = "3.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" dependencies = [ "bitflags 1.3.2", "libc", - "mach", - "winapi", + "mach2", + "windows-sys 0.52.0", ] [[package]] name = "rend" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] @@ -1905,7 +1967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.0", "getrandom", "libc", "spin", @@ -1915,30 +1977,52 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" dependencies = [ + "bitvec", "bytecheck", + "bytes", "hashbrown 0.12.3", - "indexmap 1.8.1", + "indexmap 1.9.3", "ptr_meta", "rend", "rkyv_derive", "seahash", + "tinyvec", + "uuid", ] [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] +[[package]] +name = "ruint2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b066b8e4fcea7fae86b6932d2449670b6b5545b7e8407841b2d3a916ff2a9f86" +dependencies = [ + "derive_more", + "ruint2-macro", + "rustc_version", + "thiserror", +] + +[[package]] +name = "ruint2-macro" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dc553bc0cf4512a8b96caa2e21ed5f6e4b66bf28a1bd08fd9eb07c0b39b28c" + [[package]] name = "rust-kzg-bn254" version = "0.2.0" @@ -1968,15 +2052,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "rustc-hash" -version = "1.1.0" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -1984,29 +2062,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.13", -] - -[[package]] -name = "rustix" -version = "0.38.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "semver", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -2022,26 +2088,20 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -2054,9 +2114,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "seahash" @@ -2065,34 +2125,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] -name = "semver" -version = "0.11.0" +name = "self_cell" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "semver" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" - -[[package]] -name = "semver-parser" -version = "0.10.2" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -2110,20 +2158,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.109" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2132,11 +2180,10 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.12.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "946fa04a8ac43ff78a1f4b811990afb9ddbdf5890b46d6dda0ba1998230138b7" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ - "rustversion", "serde", "serde_with_macros", ] @@ -2147,10 +2194,10 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] @@ -2160,7 +2207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", "opaque-debug", @@ -2172,7 +2219,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.10.7", ] @@ -2209,22 +2256,38 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.2.0" +name = "shared-buffer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" +dependencies = [ + "bytes", + "memmap2 0.6.2", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "siphasher" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slice-group-by" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] @@ -2265,7 +2328,7 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ - "clap 2.33.3", + "clap 2.34.0", "lazy_static", "structopt-derive", ] @@ -2276,46 +2339,81 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck 0.3.3", + "heck", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", +] + +[[package]] +name = "stylus" +version = "0.1.0" +dependencies = [ + "arbutil", + "bincode", + "brotli", + "caller-env", + "derivative", + "eyre", + "fnv", + "hex", + "lazy_static", + "libc", + "lru", + "num-bigint", + "parking_lot", + "prover", + "rand", + "thiserror", + "user-host-trait", + "wasmer", + "wasmer-compiler-cranelift", + "wasmer-compiler-llvm", + "wasmer-compiler-singlepass", + "wasmer-types", + "wasmer-vm", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.76" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "syn" -version = "2.0.45" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eae3c679c56dc214320b67a1bc04ef3dfbd6411f6443974b5e4893231298e66" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" -version = "0.12.4" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "textwrap" @@ -2328,22 +2426,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.33" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.33" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 2.0.66", ] [[package]] @@ -2352,7 +2450,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -2365,6 +2463,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2392,28 +2499,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "toml_datetime", "winnow", ] [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2422,13 +2528,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 2.0.66", ] [[package]] @@ -2468,15 +2574,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" - -[[package]] -name = "ucd-trie" -version = "0.1.5" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" @@ -2486,9 +2586,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2501,21 +2601,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.2.2" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "untrusted" @@ -2525,9 +2619,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.7" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64", "flate2", @@ -2535,22 +2629,38 @@ dependencies = [ "once_cell", "rustls", "rustls-pki-types", - "rustls-webpki", "url", "webpki-roots", ] [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "user-host-trait" +version = "0.1.0" +dependencies = [ + "arbutil", + "caller-env", + "eyre", + "prover", + "ruint2", +] + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + [[package]] name = "valuable" version = "0.1.0" @@ -2587,57 +2697,34 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.76", + "syn 2.0.66", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-downcast" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dac026d43bcca6e7ce1c0956ba68f59edf6403e8e930a5d891be72c31a44340" -dependencies = [ - "js-sys", - "once_cell", - "wasm-bindgen", - "wasm-bindgen-downcast-macros", -] - -[[package]] -name = "wasm-bindgen-downcast-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5020cfa87c7cecefef118055d44e3c1fc122c7ec25701d528ee458a0b45f38f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.76", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2645,49 +2732,50 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.76", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-encoder" -version = "0.25.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eff853c4f09eec94d76af527eddad4e9de13b11d6286a1ef7134bc30135a2b7" +checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" dependencies = [ "leb128", ] [[package]] name = "wasmer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740f96c9e5d49f0056d716977657f3f7f8eea9923b41f46d1046946707aa038f" +version = "4.2.8" dependencies = [ "bytes", - "cfg-if", - "indexmap 1.8.1", + "cfg-if 1.0.0", + "derivative", + "indexmap 1.9.3", "js-sys", "more-asserts", + "rustc-demangle", "serde", "serde-wasm-bindgen", + "shared-buffer", "target-lexicon", "thiserror", + "tracing", "wasm-bindgen", - "wasm-bindgen-downcast", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-derive", @@ -2699,38 +2787,37 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001d072dd9823e5a06052621eadb531627b4a508d74b67da4590a3d5d9332dc8" +version = "4.2.8" dependencies = [ "backtrace", - "cfg-if", + "bytes", + "cfg-if 1.0.0", "enum-iterator", "enumset", "lazy_static", "leb128", - "memmap2", + "memmap2 0.5.10", "more-asserts", "region", - "rustc-demangle", + "rkyv", + "self_cell", + "shared-buffer", "smallvec", "thiserror", "wasmer-types", "wasmer-vm", - "wasmparser 0.83.0", + "wasmparser", "winapi", ] [[package]] name = "wasmer-compiler-cranelift" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2974856a7ce40eb033efc9db3d480845385c27079b6e33ce51751f2f3c67e9bd" +version = "4.2.8" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli", + "gimli 0.26.2", "more-asserts", "rayon", "smallvec", @@ -2742,9 +2829,7 @@ dependencies = [ [[package]] name = "wasmer-compiler-llvm" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8354256545d5832658267b490948c8559dadaf6d60c5d3dde650acd84505624" +version = "4.2.8" dependencies = [ "byteorder", "cc", @@ -2756,7 +2841,7 @@ dependencies = [ "rayon", "regex", "rustc_version", - "semver 1.0.13", + "semver", "smallvec", "target-lexicon", "wasmer-compiler", @@ -2764,27 +2849,41 @@ dependencies = [ "wasmer-vm", ] +[[package]] +name = "wasmer-compiler-singlepass" +version = "4.2.8" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "enumset", + "gimli 0.26.2", + "lazy_static", + "more-asserts", + "rayon", + "smallvec", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-derive" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b23b52272494369a1f96428f0056425a85a66154610c988d971bbace8230f1" +version = "4.2.8" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.76", + "syn 1.0.109", ] [[package]] name = "wasmer-types" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bc6cd7a2d2d3bd901ff491f131188c1030694350685279e16e1233b9922846b" +version = "4.2.8" dependencies = [ + "bytecheck", "enum-iterator", "enumset", - "indexmap 1.8.1", + "indexmap 1.9.3", "more-asserts", "rkyv", "target-lexicon", @@ -2793,16 +2892,18 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67d0cd6c0ef4985d1ce9c7d7cccf34e910804417a230fa16ab7ee904efb4c34" +version = "4.2.8" dependencies = [ "backtrace", "cc", - "cfg-if", + "cfg-if 1.0.0", "corosensei", + "crossbeam-queue", + "dashmap", + "derivative", "enum-iterator", - "indexmap 1.8.1", + "fnv", + "indexmap 1.9.3", "lazy_static", "libc", "mach", @@ -2817,24 +2918,20 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.83.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" - -[[package]] -name = "wasmparser" -version = "0.84.0" +version = "0.121.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77dc97c22bb5ce49a47b745bed8812d30206eff5ef3af31424f2c1820c0974b2" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" dependencies = [ - "indexmap 1.8.1", + "bitflags 2.5.0", + "indexmap 2.2.6", + "semver", ] [[package]] name = "wast" -version = "55.0.0" +version = "64.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4984d3e1406571f4930ba5cf79bd70f75f41d0e87e17506e0bd19b0e5d085f05" +checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" dependencies = [ "leb128", "memchr", @@ -2844,18 +2941,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.61" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2b53f4da14db05d32e70e9c617abdf6620c575bd5dd972b7400037b4df2091" +checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -2863,23 +2960,23 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] [[package]] -name = "which" -version = "4.4.2" +name = "wee_alloc" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" dependencies = [ - "either", - "home", - "once_cell", - "rustix", + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", ] [[package]] @@ -2926,19 +3023,6 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2954,7 +3038,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -2974,17 +3058,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2995,9 +3080,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -3005,12 +3090,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3019,9 +3098,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -3029,12 +3108,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3043,21 +3116,21 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "windows_i686_msvc" -version = "0.33.0" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" [[package]] name = "windows_i686_msvc" @@ -3067,9 +3140,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -3077,12 +3150,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3091,9 +3158,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -3103,9 +3170,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -3113,12 +3180,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3127,19 +3188,28 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.4.7" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -3157,14 +3227,14 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn 2.0.66", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -3177,5 +3247,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.45", + "syn 2.0.66", ] diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index c63a0b514..138bdc2c6 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -1,9 +1,35 @@ [workspace] members = [ "arbutil", + "brotli", + "brotli/fuzz", + "caller-env", "prover", + "stylus", "jit", ] +exclude = [ + "stylus/tests/", + "tools/wasmer/", +] +resolver = "2" + +[workspace.package] +authors = ["Offchain Labs"] +edition = "2021" +homepage = "https://arbitrum.io" +license = "BSL" +repository = "https://github.com/OffchainLabs/nitro.git" +rust-version = "1.67" + +[workspace.dependencies] +cfg-if = "1.0.0" +lazy_static = "1.4.0" +lru = "0.12.3" +num_enum = { version = "0.7.2", default-features = false } +ruint2 = "1.9.0" +wasmparser = "0.121" +wee_alloc = "0.4.2" [profile.release] debug = true diff --git a/arbitrator/arbutil/Cargo.toml b/arbitrator/arbutil/Cargo.toml index cab0b2298..3fe1a9d13 100644 --- a/arbitrator/arbutil/Cargo.toml +++ b/arbitrator/arbutil/Cargo.toml @@ -5,6 +5,15 @@ edition = "2021" [dependencies] digest = "0.10.7" -num_enum = "0.7.0" +eyre = "0.6.5" +fnv = "1.0.7" +hex = "0.4.3" +num-traits = "0.2.17" +siphasher = "0.3.10" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } +ruint2.workspace = true +wasmparser.workspace = true +serde = { version = "1.0.130", features = ["derive", "rc"] } +num_enum = "0.7.1" sha2 = "0.10.7" sha3 = "0.10.8" diff --git a/arbitrator/arbutil/src/color.rs b/arbitrator/arbutil/src/color.rs index c3b074554..1ef6786a3 100644 --- a/arbitrator/arbutil/src/color.rs +++ b/arbitrator/arbutil/src/color.rs @@ -14,6 +14,7 @@ pub const RED: &str = "\x1b[31;1m"; pub const CLEAR: &str = "\x1b[0;0m"; pub const WHITE: &str = "\x1b[0;1m"; pub const YELLOW: &str = "\x1b[33;1m"; +pub const ORANGE: &str = "\x1b[38;5;202;1m"; pub trait Color { fn color(&self, color: &str) -> String; @@ -27,6 +28,7 @@ pub trait Color { fn red(&self) -> String; fn white(&self) -> String; fn yellow(&self) -> String; + fn orange(&self) -> String; } #[rustfmt::skip] @@ -45,6 +47,7 @@ impl Color for T where T: Display { fn red(&self) -> String { self.color(RED) } fn white(&self) -> String { self.color(WHITE) } fn yellow(&self) -> String { self.color(YELLOW) } + fn orange(&self) -> String { self.color(ORANGE) } } pub fn when(cond: bool, text: T, when_color: &str) -> String { @@ -66,6 +69,7 @@ pub trait DebugColor { fn debug_red(&self) -> String; fn debug_white(&self) -> String; fn debug_yellow(&self) -> String; + fn debug_orange(&self) -> String; } #[rustfmt::skip] @@ -84,4 +88,5 @@ impl DebugColor for T where T: Debug { fn debug_red(&self) -> String { self.debug_color(RED) } fn debug_white(&self) -> String { self.debug_color(WHITE) } fn debug_yellow(&self) -> String { self.debug_color(YELLOW) } + fn debug_orange(&self) -> String { self.debug_color(ORANGE) } } diff --git a/arbitrator/arbutil/src/crypto.rs b/arbitrator/arbutil/src/crypto.rs new file mode 100644 index 000000000..3f5f57ca8 --- /dev/null +++ b/arbitrator/arbutil/src/crypto.rs @@ -0,0 +1,25 @@ +// Copyright 2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use siphasher::sip::SipHasher24; +use std::mem::MaybeUninit; +use tiny_keccak::{Hasher, Keccak}; + +pub fn keccak>(preimage: T) -> [u8; 32] { + let mut output = MaybeUninit::<[u8; 32]>::uninit(); + let mut hasher = Keccak::v256(); + hasher.update(preimage.as_ref()); + + // SAFETY: finalize() writes 32 bytes + unsafe { + hasher.finalize(&mut *output.as_mut_ptr()); + output.assume_init() + } +} + +pub fn siphash(preimage: &[u8], key: &[u8; 16]) -> u64 { + use std::hash::Hasher; + let mut hasher = SipHasher24::new_with_key(key); + hasher.write(preimage); + hasher.finish() +} diff --git a/arbitrator/arbutil/src/evm/api.rs b/arbitrator/arbutil/src/evm/api.rs new file mode 100644 index 000000000..093e7f298 --- /dev/null +++ b/arbitrator/arbutil/src/evm/api.rs @@ -0,0 +1,192 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{evm::user::UserOutcomeKind, Bytes20, Bytes32}; +use eyre::Result; +use num_enum::IntoPrimitive; +use std::sync::Arc; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive)] +#[repr(u8)] +pub enum EvmApiStatus { + Success, + Failure, + OutOfGas, + WriteProtection, +} + +impl From for EvmApiStatus { + fn from(value: u8) -> Self { + match value { + 0 => Self::Success, + 2 => Self::OutOfGas, + 3 => Self::WriteProtection, + _ => Self::Failure, + } + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u32)] +pub enum EvmApiMethod { + GetBytes32, + SetTrieSlots, + GetTransientBytes32, + SetTransientBytes32, + ContractCall, + DelegateCall, + StaticCall, + Create1, + Create2, + EmitLog, + AccountBalance, + AccountCode, + AccountCodeHash, + AddPages, + CaptureHostIO, +} + +/// This offset is added to EvmApiMethod when sending a request +/// in WASM - program done is also indicated by a "request", with the +/// id below that offset, indicating program status +pub const EVM_API_METHOD_REQ_OFFSET: u32 = 0x10000000; + +/// Copies data from Go into Rust. +/// Note: clone should not clone actual data, just the reader. +pub trait DataReader: Clone + Send + 'static { + fn slice(&self) -> &[u8]; +} + +/// Simple implementation for `DataReader`, in case data comes from a `Vec`. +#[derive(Clone, Debug)] +pub struct VecReader(Arc>); + +impl VecReader { + pub fn new(data: Vec) -> Self { + Self(Arc::new(data)) + } +} + +impl DataReader for VecReader { + fn slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +pub trait EvmApi: Send + 'static { + /// Reads the 32-byte value in the EVM state trie at offset `key`. + /// Returns the value and the access cost in gas. + /// Analogous to `vm.SLOAD`. + fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64); + + /// Stores the given value at the given key in Stylus VM's cache of the EVM state trie. + /// Note that the actual values only get written after calls to `set_trie_slots`. + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64; + + /// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested. + /// Analogous to repeated invocations of `vm.SSTORE`. + fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result; + + /// Reads the 32-byte value in the EVM's transient state trie at offset `key`. + /// Analogous to `vm.TLOAD`. + fn get_transient_bytes32(&mut self, key: Bytes32) -> Bytes32; + + /// Writes the 32-byte value in the EVM's transient state trie at offset `key`. + /// Analogous to `vm.TSTORE`. + fn set_transient_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result<()>; + + /// Calls the contract at the given address. + /// Returns the EVM return data's length, the gas cost, and whether the call succeeded. + /// Analogous to `vm.CALL`. + fn contract_call( + &mut self, + contract: Bytes20, + calldata: &[u8], + gas_left: u64, + gas_req: u64, + value: Bytes32, + ) -> (u32, u64, UserOutcomeKind); + + /// Delegate-calls the contract at the given address. + /// Returns the EVM return data's length, the gas cost, and whether the call succeeded. + /// Analogous to `vm.DELEGATECALL`. + fn delegate_call( + &mut self, + contract: Bytes20, + calldata: &[u8], + gas_left: u64, + gas_req: u64, + ) -> (u32, u64, UserOutcomeKind); + + /// Static-calls the contract at the given address. + /// Returns the EVM return data's length, the gas cost, and whether the call succeeded. + /// Analogous to `vm.STATICCALL`. + fn static_call( + &mut self, + contract: Bytes20, + calldata: &[u8], + gas_left: u64, + gas_req: u64, + ) -> (u32, u64, UserOutcomeKind); + + /// Deploys a new contract using the init code provided. + /// Returns the new contract's address on success, or the error reason on failure. + /// In both cases the EVM return data's length and the overall gas cost are returned too. + /// Analogous to `vm.CREATE`. + fn create1( + &mut self, + code: Vec, + endowment: Bytes32, + gas: u64, + ) -> (eyre::Result, u32, u64); + + /// Deploys a new contract using the init code provided, with an address determined in part by the `salt`. + /// Returns the new contract's address on success, or the error reason on failure. + /// In both cases the EVM return data's length and the overall gas cost are returned too. + /// Analogous to `vm.CREATE2`. + fn create2( + &mut self, + code: Vec, + endowment: Bytes32, + salt: Bytes32, + gas: u64, + ) -> (eyre::Result, u32, u64); + + /// Returns the EVM return data. + /// Analogous to `vm.RETURNDATACOPY`. + fn get_return_data(&self) -> D; + + /// Emits an EVM log with the given number of topics and data, the first bytes of which should be the topic data. + /// Returns an error message on failure. + /// Analogous to `vm.LOG(n)` where n ∈ [0, 4]. + fn emit_log(&mut self, data: Vec, topics: u32) -> Result<()>; + + /// Gets the balance of the given account. + /// Returns the balance and the access cost in gas. + /// Analogous to `vm.BALANCE`. + fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64); + + /// Returns the code and the access cost in gas. + /// Analogous to `vm.EXTCODECOPY`. + fn account_code(&mut self, address: Bytes20, gas_left: u64) -> (D, u64); + + /// Gets the hash of the given address's code. + /// Returns the hash and the access cost in gas. + /// Analogous to `vm.EXTCODEHASH`. + fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64); + + /// Determines the cost in gas of allocating additional wasm pages. + /// Note: has the side effect of updating Geth's memory usage tracker. + /// Not analogous to any EVM opcode. + fn add_pages(&mut self, pages: u16) -> u64; + + /// Captures tracing information for hostio invocations during native execution. + fn capture_hostio( + &mut self, + name: &str, + args: &[u8], + outs: &[u8], + start_ink: u64, + end_ink: u64, + ); +} diff --git a/arbitrator/arbutil/src/evm/mod.rs b/arbitrator/arbutil/src/evm/mod.rs new file mode 100644 index 000000000..1671e6707 --- /dev/null +++ b/arbitrator/arbutil/src/evm/mod.rs @@ -0,0 +1,101 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{Bytes20, Bytes32}; + +pub mod api; +pub mod req; +pub mod storage; +pub mod user; + +// params.SstoreSentryGasEIP2200 +pub const SSTORE_SENTRY_GAS: u64 = 2300; + +// params.ColdAccountAccessCostEIP2929 +pub const COLD_ACCOUNT_GAS: u64 = 2600; + +// params.ColdSloadCostEIP2929 +pub const COLD_SLOAD_GAS: u64 = 2100; + +// params.WarmStorageReadCostEIP2929 +pub const WARM_SLOAD_GAS: u64 = 100; + +// params.WarmStorageReadCostEIP2929 (see enable1153 in jump_table.go) +pub const TLOAD_GAS: u64 = WARM_SLOAD_GAS; +pub const TSTORE_GAS: u64 = WARM_SLOAD_GAS; + +// params.LogGas and params.LogDataGas +pub const LOG_TOPIC_GAS: u64 = 375; +pub const LOG_DATA_GAS: u64 = 8; + +// params.CopyGas +pub const COPY_WORD_GAS: u64 = 3; + +// params.Keccak256Gas +pub const KECCAK_256_GAS: u64 = 30; +pub const KECCAK_WORD_GAS: u64 = 6; + +// vm.GasQuickStep (see gas.go) +pub const GAS_QUICK_STEP: u64 = 2; + +// vm.GasQuickStep (see jump_table.go) +pub const ADDRESS_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see eips.go) +pub const BASEFEE_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see eips.go) +pub const CHAINID_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const COINBASE_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const GASLIMIT_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const NUMBER_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const TIMESTAMP_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const GASLEFT_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const CALLER_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const CALLVALUE_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const GASPRICE_GAS: u64 = GAS_QUICK_STEP; + +// vm.GasQuickStep (see jump_table.go) +pub const ORIGIN_GAS: u64 = GAS_QUICK_STEP; + +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct EvmData { + pub block_basefee: Bytes32, + pub chainid: u64, + pub block_coinbase: Bytes20, + pub block_gas_limit: u64, + pub block_number: u64, + pub block_timestamp: u64, + pub contract_address: Bytes20, + pub module_hash: Bytes32, + pub msg_sender: Bytes20, + pub msg_value: Bytes32, + pub tx_gas_price: Bytes32, + pub tx_origin: Bytes20, + pub reentrant: u32, + pub return_data_len: u32, + pub cached: bool, + pub tracing: bool, +} + +/// Returns the minimum number of EVM words needed to store `bytes` bytes. +pub fn evm_words(bytes: u32) -> u32 { + crate::math::div_ceil::<32>(bytes as usize) as u32 +} diff --git a/arbitrator/arbutil/src/evm/req.rs b/arbitrator/arbutil/src/evm/req.rs new file mode 100644 index 000000000..b1c8d9997 --- /dev/null +++ b/arbitrator/arbutil/src/evm/req.rs @@ -0,0 +1,301 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ + evm::{ + api::{DataReader, EvmApi, EvmApiMethod, EvmApiStatus}, + storage::{StorageCache, StorageWord}, + user::UserOutcomeKind, + }, + format::Utf8OrHex, + pricing::EVM_API_INK, + Bytes20, Bytes32, +}; +use eyre::{bail, eyre, Result}; +use std::collections::hash_map::Entry; + +pub trait RequestHandler: Send + 'static { + fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, u64); +} + +pub struct EvmApiRequestor> { + handler: H, + last_code: Option<(Bytes20, D)>, + last_return_data: Option, + storage_cache: StorageCache, +} + +impl> EvmApiRequestor { + pub fn new(handler: H) -> Self { + Self { + handler, + last_code: None, + last_return_data: None, + storage_cache: StorageCache::default(), + } + } + + fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, u64) { + self.handler.request(req_type, req_data) + } + + /// Call out to a contract. + fn call_request( + &mut self, + call_type: EvmApiMethod, + contract: Bytes20, + input: &[u8], + gas_left: u64, + gas_req: u64, + value: Bytes32, + ) -> (u32, u64, UserOutcomeKind) { + let mut request = Vec::with_capacity(20 + 32 + 8 + 8 + input.len()); + request.extend(contract); + request.extend(value); + request.extend(gas_left.to_be_bytes()); + request.extend(gas_req.to_be_bytes()); + request.extend(input); + + let (res, data, cost) = self.request(call_type, &request); + let status: UserOutcomeKind = res[0].try_into().expect("unknown outcome"); + let data_len = data.slice().len() as u32; + self.last_return_data = Some(data); + (data_len, cost, status) + } + + pub fn request_handler(&mut self) -> &mut H { + &mut self.handler + } + + fn create_request( + &mut self, + create_type: EvmApiMethod, + code: Vec, + endowment: Bytes32, + salt: Option, + gas: u64, + ) -> (Result, u32, u64) { + let mut request = Vec::with_capacity(8 + 2 * 32 + code.len()); + request.extend(gas.to_be_bytes()); + request.extend(endowment); + if let Some(salt) = salt { + request.extend(salt); + } + request.extend(code); + + let (mut res, data, cost) = self.request(create_type, request); + if res.len() != 21 || res[0] == 0 { + if !res.is_empty() { + res.remove(0); + } + let err_string = String::from_utf8(res).unwrap_or("create_response_malformed".into()); + return (Err(eyre!(err_string)), 0, cost); + } + res.remove(0); + let address = res.try_into().unwrap(); + let data_len = data.slice().len() as u32; + self.last_return_data = Some(data); + (Ok(address), data_len, cost) + } +} + +impl> EvmApi for EvmApiRequestor { + fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + let cache = &mut self.storage_cache; + let mut cost = cache.read_gas(); + + let value = cache.entry(key).or_insert_with(|| { + let (res, _, gas) = self.handler.request(EvmApiMethod::GetBytes32, key); + cost = cost.saturating_add(gas).saturating_add(EVM_API_INK); + StorageWord::known(res.try_into().unwrap()) + }); + (value.value, cost) + } + + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + let cost = self.storage_cache.write_gas(); + match self.storage_cache.entry(key) { + Entry::Occupied(mut key) => key.get_mut().value = value, + Entry::Vacant(slot) => drop(slot.insert(StorageWord::unknown(value))), + }; + cost + } + + fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result { + let mut data = Vec::with_capacity(64 * self.storage_cache.len() + 8); + data.extend(gas_left.to_be_bytes()); + + for (key, value) in &mut self.storage_cache.slots { + if value.dirty() { + data.extend(*key); + data.extend(*value.value); + value.known = Some(value.value); + } + } + if clear { + self.storage_cache.clear(); + } + if data.len() == 8 { + return Ok(0); // no need to make request + } + + let (res, _, cost) = self.request(EvmApiMethod::SetTrieSlots, data); + if res[0] != EvmApiStatus::Success.into() { + bail!("{}", String::from_utf8_or_hex(res)); + } + Ok(cost) + } + + fn get_transient_bytes32(&mut self, key: Bytes32) -> Bytes32 { + let (res, ..) = self.request(EvmApiMethod::GetTransientBytes32, key); + res.try_into().unwrap() + } + + fn set_transient_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result<()> { + let mut data = Vec::with_capacity(64); + data.extend(key); + data.extend(value); + let (res, ..) = self.request(EvmApiMethod::SetTransientBytes32, data); + if res[0] != EvmApiStatus::Success.into() { + bail!("{}", String::from_utf8_or_hex(res)); + } + Ok(()) + } + + fn contract_call( + &mut self, + contract: Bytes20, + input: &[u8], + gas_left: u64, + gas_req: u64, + value: Bytes32, + ) -> (u32, u64, UserOutcomeKind) { + self.call_request( + EvmApiMethod::ContractCall, + contract, + input, + gas_left, + gas_req, + value, + ) + } + + fn delegate_call( + &mut self, + contract: Bytes20, + input: &[u8], + gas_left: u64, + gas_req: u64, + ) -> (u32, u64, UserOutcomeKind) { + self.call_request( + EvmApiMethod::DelegateCall, + contract, + input, + gas_left, + gas_req, + Bytes32::default(), + ) + } + + fn static_call( + &mut self, + contract: Bytes20, + input: &[u8], + gas_left: u64, + gas_req: u64, + ) -> (u32, u64, UserOutcomeKind) { + self.call_request( + EvmApiMethod::StaticCall, + contract, + input, + gas_left, + gas_req, + Bytes32::default(), + ) + } + + fn create1( + &mut self, + code: Vec, + endowment: Bytes32, + gas: u64, + ) -> (Result, u32, u64) { + self.create_request(EvmApiMethod::Create1, code, endowment, None, gas) + } + + fn create2( + &mut self, + code: Vec, + endowment: Bytes32, + salt: Bytes32, + gas: u64, + ) -> (Result, u32, u64) { + self.create_request(EvmApiMethod::Create2, code, endowment, Some(salt), gas) + } + + fn get_return_data(&self) -> D { + self.last_return_data.clone().expect("missing return data") + } + + fn emit_log(&mut self, data: Vec, topics: u32) -> Result<()> { + // TODO: remove copy + let mut request = Vec::with_capacity(4 + data.len()); + request.extend(topics.to_be_bytes()); + request.extend(data); + + let (res, _, _) = self.request(EvmApiMethod::EmitLog, request); + if !res.is_empty() { + bail!(String::from_utf8(res).unwrap_or("malformed emit-log response".into())) + } + Ok(()) + } + + fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64) { + let (res, _, cost) = self.request(EvmApiMethod::AccountBalance, address); + (res.try_into().unwrap(), cost) + } + + fn account_code(&mut self, address: Bytes20, gas_left: u64) -> (D, u64) { + if let Some((stored_address, data)) = self.last_code.as_ref() { + if address == *stored_address { + return (data.clone(), 0); + } + } + let mut req = Vec::with_capacity(20 + 8); + req.extend(address); + req.extend(gas_left.to_be_bytes()); + + let (_, data, cost) = self.request(EvmApiMethod::AccountCode, req); + self.last_code = Some((address, data.clone())); + (data, cost) + } + + fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64) { + let (res, _, cost) = self.request(EvmApiMethod::AccountCodeHash, address); + (res.try_into().unwrap(), cost) + } + + fn add_pages(&mut self, pages: u16) -> u64 { + self.request(EvmApiMethod::AddPages, pages.to_be_bytes()).2 + } + + fn capture_hostio( + &mut self, + name: &str, + args: &[u8], + outs: &[u8], + start_ink: u64, + end_ink: u64, + ) { + let mut request = Vec::with_capacity(2 * 8 + 3 * 2 + name.len() + args.len() + outs.len()); + request.extend(start_ink.to_be_bytes()); + request.extend(end_ink.to_be_bytes()); + request.extend((name.len() as u16).to_be_bytes()); + request.extend((args.len() as u16).to_be_bytes()); + request.extend((outs.len() as u16).to_be_bytes()); + request.extend(name.as_bytes()); + request.extend(args); + request.extend(outs); + self.request(EvmApiMethod::CaptureHostIO, request); + } +} diff --git a/arbitrator/arbutil/src/evm/storage.rs b/arbitrator/arbutil/src/evm/storage.rs new file mode 100644 index 000000000..32b60dd21 --- /dev/null +++ b/arbitrator/arbutil/src/evm/storage.rs @@ -0,0 +1,73 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::Bytes32; +use fnv::FnvHashMap as HashMap; +use std::ops::{Deref, DerefMut}; + +/// Represents the EVM word at a given key. +#[derive(Debug)] +pub struct StorageWord { + /// The current value of the slot. + pub value: Bytes32, + /// The value in Geth, if known. + pub known: Option, +} + +impl StorageWord { + pub fn known(value: Bytes32) -> Self { + let known = Some(value); + Self { value, known } + } + + pub fn unknown(value: Bytes32) -> Self { + Self { value, known: None } + } + + pub fn dirty(&self) -> bool { + Some(self.value) != self.known + } +} + +#[derive(Default)] +pub struct StorageCache { + pub(crate) slots: HashMap, + reads: usize, + writes: usize, +} + +impl StorageCache { + pub const REQUIRED_ACCESS_GAS: u64 = 10; + + pub fn read_gas(&mut self) -> u64 { + self.reads += 1; + match self.reads { + 0..=32 => 0, + 33..=128 => 2, + _ => 10, + } + } + + pub fn write_gas(&mut self) -> u64 { + self.writes += 1; + match self.writes { + 0..=8 => 0, + 9..=64 => 7, + _ => 10, + } + } +} + +impl Deref for StorageCache { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.slots + } +} + +impl DerefMut for StorageCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.slots + } +} diff --git a/arbitrator/arbutil/src/evm/user.rs b/arbitrator/arbutil/src/evm/user.rs new file mode 100644 index 000000000..c3673010c --- /dev/null +++ b/arbitrator/arbutil/src/evm/user.rs @@ -0,0 +1,91 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use eyre::ErrReport; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::fmt::Display; + +#[derive(Debug)] +pub enum UserOutcome { + Success(Vec), + Revert(Vec), + Failure(ErrReport), + OutOfInk, + OutOfStack, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[repr(u8)] +pub enum UserOutcomeKind { + Success, + Revert, + Failure, + OutOfInk, + OutOfStack, +} + +impl UserOutcome { + pub fn into_data(self) -> (UserOutcomeKind, Vec) { + let kind = self.kind(); + let data = match self { + Self::Success(out) => out, + Self::Revert(out) => out, + Self::Failure(err) => format!("{err:?}").as_bytes().to_vec(), + _ => vec![], + }; + (kind, data) + } + + pub fn kind(&self) -> UserOutcomeKind { + self.into() + } +} + +impl From<&UserOutcome> for UserOutcomeKind { + fn from(value: &UserOutcome) -> Self { + use UserOutcome::*; + match value { + Success(_) => Self::Success, + Revert(_) => Self::Revert, + Failure(_) => Self::Failure, + OutOfInk => Self::OutOfInk, + OutOfStack => Self::OutOfStack, + } + } +} + +impl From<&UserOutcome> for u8 { + fn from(value: &UserOutcome) -> Self { + UserOutcomeKind::from(value).into() + } +} + +impl Display for UserOutcome { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use UserOutcome::*; + match self { + Success(data) => write!(f, "success {}", hex::encode(data)), + Failure(err) => write!(f, "failure {:?}", err), + OutOfInk => write!(f, "out of ink"), + OutOfStack => write!(f, "out of stack"), + Revert(data) => { + let text = String::from_utf8(data.clone()).unwrap_or_else(|_| hex::encode(data)); + write!(f, "revert {text}") + } + } + } +} + +impl Display for UserOutcomeKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let as_u8 = *self as u8; + use UserOutcomeKind::*; + match self { + Success => write!(f, "success ({as_u8})"), + Revert => write!(f, "revert ({as_u8})"), + Failure => write!(f, "failure ({as_u8})"), + OutOfInk => write!(f, "out of ink ({as_u8})"), + OutOfStack => write!(f, "out of stack ({as_u8})"), + } + } +} diff --git a/arbitrator/arbutil/src/format.rs b/arbitrator/arbutil/src/format.rs index 2a5a04988..de4c0968e 100644 --- a/arbitrator/arbutil/src/format.rs +++ b/arbitrator/arbutil/src/format.rs @@ -1,8 +1,31 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::color::Color; -use std::fmt::Display; +use std::{ + fmt::{Debug, Display}, + time::Duration, +}; + +#[must_use] +pub fn time(span: Duration) -> String { + use crate::color::{MINT, RED, YELLOW}; + + let mut span = span.as_nanos() as f64; + let mut unit = 0; + let units = [ + "ns", "μs", "ms", "s", "min", "h", "d", "w", "mo", "yr", "dec", "cent", "mill", "eon", + ]; + let scale = [ + 1000., 1000., 1000., 60., 60., 24., 7., 4.34, 12., 10., 10., 10., 1_000_000., + ]; + let colors = [MINT, MINT, YELLOW, RED, RED, RED]; + while span >= scale[unit] && unit < scale.len() { + span /= scale[unit]; + unit += 1; + } + format!("{:6}", format!("{:.1}{}", span, units[unit])).color(colors[unit]) +} #[must_use] pub fn commas(items: U) -> String @@ -13,3 +36,30 @@ where let items: Vec<_> = items.into_iter().map(|x| format!("{x}")).collect(); items.join(&", ".grey()) } + +pub fn hex_fmt>(data: T, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(&hex::encode(data)) +} + +pub trait DebugBytes { + fn debug_bytes(self) -> Vec; +} + +impl DebugBytes for T { + fn debug_bytes(self) -> Vec { + format!("{:?}", self).as_bytes().to_vec() + } +} + +pub trait Utf8OrHex { + fn from_utf8_or_hex(data: impl Into>) -> String; +} + +impl Utf8OrHex for String { + fn from_utf8_or_hex(data: impl Into>) -> String { + match String::from_utf8(data.into()) { + Ok(string) => string, + Err(error) => hex::encode(error.as_bytes()), + } + } +} diff --git a/arbitrator/arbutil/src/lib.rs b/arbitrator/arbutil/src/lib.rs index aa748b84e..9c48a9fef 100644 --- a/arbitrator/arbutil/src/lib.rs +++ b/arbitrator/arbutil/src/lib.rs @@ -1,9 +1,50 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +/// cbindgen:ignore pub mod color; +pub mod crypto; +pub mod evm; pub mod format; -mod types; +pub mod math; +pub mod operator; +pub mod pricing; +pub mod types; pub use color::{Color, DebugColor}; -pub use types::PreimageType; +use num_traits::Unsigned; +pub use types::{Bytes20, Bytes32, PreimageType}; + +/// Puts an arbitrary type on the heap. +/// Note: the type must be later freed or the value will be leaked. +pub fn heapify(value: T) -> *mut T { + Box::into_raw(Box::new(value)) +} + +/// Equivalent to &[start..offset], but truncates when out of bounds rather than panicking. +pub fn slice_with_runoff(data: &impl AsRef<[T]>, start: I, end: I) -> &[T] +where + I: TryInto + Unsigned, +{ + let start = start.try_into().unwrap_or(usize::MAX); + let end = end.try_into().unwrap_or(usize::MAX); + + let data = data.as_ref(); + if start >= data.len() || end < start { + return &[]; + } + &data[start..end.min(data.len())] +} + +#[test] +fn test_limit_vec() { + let testvec = vec![0, 1, 2, 3]; + assert_eq!(slice_with_runoff(&testvec, 4_u32, 4), &testvec[0..0]); + assert_eq!(slice_with_runoff(&testvec, 1_u16, 0), &testvec[0..0]); + assert_eq!(slice_with_runoff(&testvec, 0_u64, 0), &testvec[0..0]); + assert_eq!(slice_with_runoff(&testvec, 0_u32, 1), &testvec[0..1]); + assert_eq!(slice_with_runoff(&testvec, 1_u64, 3), &testvec[1..3]); + assert_eq!(slice_with_runoff(&testvec, 0_u16, 4), &testvec[0..4]); + assert_eq!(slice_with_runoff(&testvec, 0_u8, 5), &testvec[0..4]); + assert_eq!(slice_with_runoff(&testvec, 2, usize::MAX), &testvec[2..4]); +} diff --git a/arbitrator/arbutil/src/math.rs b/arbitrator/arbutil/src/math.rs new file mode 100644 index 000000000..72d502539 --- /dev/null +++ b/arbitrator/arbutil/src/math.rs @@ -0,0 +1,43 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use num_traits::{ops::saturating::SaturatingAdd, Zero}; +use std::ops::{BitAnd, Sub}; + +/// Checks if a number is a power of 2. +pub fn is_power_of_2(value: T) -> bool +where + T: Sub + BitAnd + PartialOrd + From + Copy, +{ + if value <= 0.into() { + return false; + } + value & (value - 1.into()) == 0.into() +} + +/// Calculates a sum, saturating in cases of overflow. +pub trait SaturatingSum { + type Number; + + fn saturating_sum(self) -> Self::Number; +} + +impl SaturatingSum for I +where + I: Iterator, + T: SaturatingAdd + Zero, +{ + type Number = T; + + fn saturating_sum(self) -> Self::Number { + self.fold(T::zero(), |acc, x| acc.saturating_add(&x)) + } +} + +/// Returns `num` divided by `N`, rounded up. +pub fn div_ceil(num: usize) -> usize { + match num % N { + 0 => num / N, + _ => num / N + 1, + } +} diff --git a/arbitrator/arbutil/src/operator.rs b/arbitrator/arbutil/src/operator.rs new file mode 100644 index 000000000..cc1f68436 --- /dev/null +++ b/arbitrator/arbutil/src/operator.rs @@ -0,0 +1,1209 @@ +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::Hash; +use wasmparser::Operator; + +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct OperatorCode(usize); + +impl OperatorCode { + // TODO: use std::mem::variant_count when it's stabilized + pub const OPERATOR_COUNT: usize = 529; +} + +impl Display for OperatorCode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let name = match self.0 { + 0x00 => "Unreachable", + 0x01 => "Nop", + 0x02 => "Block", + 0x03 => "Loop", + 0x04 => "If", + 0x05 => "Else", + 0x06 => "Try", + 0x07 => "Catch", + 0x08 => "Throw", + 0x09 => "Rethrow", + 0x0a => "ThrowRef", + 0x0b => "End", + 0x0c => "Br", + 0x0d => "BrIf", + 0x0e => "BrTable", + 0x0f => "Return", + 0x10 => "Call", + 0x11 => "CallIndirect", + 0x12 => "ReturnCall", + 0x13 => "ReturnCallIndirect", + 0x14 => "CallRef", + 0x15 => "ReturnCallRef", + 0x18 => "Delegate", + 0x19 => "CatchAll", + 0x1a => "Drop", + 0x1b => "Select", + 0x1c => "TypedSelect", + 0x1f => "TryTable", + 0x20 => "LocalGet", + 0x21 => "LocalSet", + 0x22 => "LocalTee", + 0x23 => "GlobalGet", + 0x24 => "GlobalSet", + 0x25 => "TableGet", + 0x26 => "TableSet", + 0x28 => "I32Load", + 0x29 => "I64Load", + 0x2a => "F32Load", + 0x2b => "F64Load", + 0x2c => "I32Load8S", + 0x2d => "I32Load8U", + 0x2e => "I32Load16S", + 0x2f => "I32Load16U", + 0x30 => "I64Load8S", + 0x31 => "I64Load8U", + 0x32 => "I64Load16S", + 0x33 => "I64Load16U", + 0x34 => "I64Load32S", + 0x35 => "I64Load32U", + 0x36 => "I32Store", + 0x37 => "I64Store", + 0x38 => "F32Store", + 0x39 => "F64Store", + 0x3a => "I32Store8", + 0x3b => "I32Store16", + 0x3c => "I64Store8", + 0x3d => "I64Store16", + 0x3e => "I64Store32", + 0x3f => "MemorySize", + 0x40 => "MemoryGrow", + 0x41 => "I32Const", + 0x42 => "I64Const", + 0x43 => "F32Const", + 0x44 => "F64Const", + 0x45 => "I32Eqz", + 0x46 => "I32Eq", + 0x47 => "I32Ne", + 0x48 => "I32LtS", + 0x49 => "I32LtU", + 0x4a => "I32GtS", + 0x4b => "I32GtU", + 0x4c => "I32LeS", + 0x4d => "I32LeU", + 0x4e => "I32GeS", + 0x4f => "I32GeU", + 0x50 => "I64Eqz", + 0x51 => "I64Eq", + 0x52 => "I64Ne", + 0x53 => "I64LtS", + 0x54 => "I64LtU", + 0x55 => "I64GtS", + 0x56 => "I64GtU", + 0x57 => "I64LeS", + 0x58 => "I64LeU", + 0x59 => "I64GeS", + 0x5a => "I64GeU", + 0x5b => "F32Eq", + 0x5c => "F32Ne", + 0x5d => "F32Lt", + 0x5e => "F32Gt", + 0x5f => "F32Le", + 0x60 => "F32Ge", + 0x61 => "F64Eq", + 0x62 => "F64Ne", + 0x63 => "F64Lt", + 0x64 => "F64Gt", + 0x65 => "F64Le", + 0x66 => "F64Ge", + 0x67 => "I32Clz", + 0x68 => "I32Ctz", + 0x69 => "I32Popcnt", + 0x6a => "I32Add", + 0x6b => "I32Sub", + 0x6c => "I32Mul", + 0x6d => "I32DivS", + 0x6e => "I32DivU", + 0x6f => "I32RemS", + 0x70 => "I32RemU", + 0x71 => "I32And", + 0x72 => "I32Or", + 0x73 => "I32Xor", + 0x74 => "I32Shl", + 0x75 => "I32ShrS", + 0x76 => "I32ShrU", + 0x77 => "I32Rotl", + 0x78 => "I32Rotr", + 0x79 => "I64Clz", + 0x7a => "I64Ctz", + 0x7b => "I64Popcnt", + 0x7c => "I64Add", + 0x7d => "I64Sub", + 0x7e => "I64Mul", + 0x7f => "I64DivS", + 0x80 => "I64DivU", + 0x81 => "I64RemS", + 0x82 => "I64RemU", + 0x83 => "I64And", + 0x84 => "I64Or", + 0x85 => "I64Xor", + 0x86 => "I64Shl", + 0x87 => "I64ShrS", + 0x88 => "I64ShrU", + 0x89 => "I64Rotl", + 0x8a => "I64Rotr", + 0x8b => "F32Abs", + 0x8c => "F32Neg", + 0x8d => "F32Ceil", + 0x8e => "F32Floor", + 0x8f => "F32Trunc", + 0x90 => "F32Nearest", + 0x91 => "F32Sqrt", + 0x92 => "F32Add", + 0x93 => "F32Sub", + 0x94 => "F32Mul", + 0x95 => "F32Div", + 0x96 => "F32Min", + 0x97 => "F32Max", + 0x98 => "F32Copysign", + 0x99 => "F64Abs", + 0x9a => "F64Neg", + 0x9b => "F64Ceil", + 0x9c => "F64Floor", + 0x9d => "F64Trunc", + 0x9e => "F64Nearest", + 0x9f => "F64Sqrt", + 0xa0 => "F64Add", + 0xa1 => "F64Sub", + 0xa2 => "F64Mul", + 0xa3 => "F64Div", + 0xa4 => "F64Min", + 0xa5 => "F64Max", + 0xa6 => "F64Copysign", + 0xa7 => "I32WrapI64", + 0xa8 => "I32TruncF32S", + 0xa9 => "I32TruncF32U", + 0xaa => "I32TruncF64S", + 0xab => "I32TruncF64U", + 0xac => "I64ExtendI32S", + 0xad => "I64ExtendI32U", + 0xae => "I64TruncF32S", + 0xaf => "I64TruncF32U", + 0xb0 => "I64TruncF64S", + 0xb1 => "I64TruncF64U", + 0xb2 => "F32ConvertI32S", + 0xb3 => "F32ConvertI32U", + 0xb4 => "F32ConvertI64S", + 0xb5 => "F32ConvertI64U", + 0xb6 => "F32DemoteF64", + 0xb7 => "F64ConvertI32S", + 0xb8 => "F64ConvertI32U", + 0xb9 => "F64ConvertI64S", + 0xba => "F64ConvertI64U", + 0xbb => "F64PromoteF32", + 0xbc => "I32ReinterpretF32", + 0xbd => "I64ReinterpretF64", + 0xbe => "F32ReinterpretI32", + 0xbf => "F64ReinterpretI64", + 0xc0 => "I32Extend8S", + 0xc1 => "I32Extend16S", + 0xc2 => "I64Extend8S", + 0xc3 => "I64Extend16S", + 0xc4 => "I64Extend32S", + 0xd0 => "RefNull", + 0xd1 => "RefIsNull", + 0xd2 => "RefFunc", + 0xd3 => "RefAsNonNull", + 0xd4 => "BrOnNull", + 0xd5 => "RefEq", + 0xd6 => "BrOnNonNull", + 0xfb00 => "StructNew", + 0xfb01 => "StructNewDefault", + 0xfb02 => "StructGet", + 0xfb03 => "StructGetS", + 0xfb04 => "StructGetU", + 0xfb05 => "StructSet", + 0xfb06 => "ArrayNew", + 0xfb07 => "ArrayNewDefault", + 0xfb08 => "ArrayNewFixed", + 0xfb09 => "ArrayNewData", + 0xfb0a => "ArrayNewElem", + 0xfb0b => "ArrayGet", + 0xfb0c => "ArrayGetS", + 0xfb0d => "ArrayGetU", + 0xfb0e => "ArraySet", + 0xfb0f => "ArrayLen", + 0xfb10 => "ArrayFill", + 0xfb11 => "ArrayCopy", + 0xfb12 => "ArrayInitData", + 0xfb13 => "ArrayInitElem", + 0xfb14 => "RefTestNonNull", + 0xfb15 => "RefTestNullable", + 0xfb16 => "RefCastNonNull", + 0xfb17 => "RefCastNullable", + 0xfb18 => "BrOnCast", + 0xfb19 => "BrOnCastFail", + 0xfb1a => "AnyConvertExtern", + 0xfb1b => "ExternConvertAny", + 0xfb1c => "RefI31", + 0xfb1d => "I31GetS", + 0xfb1e => "I31GetU", + 0xfc00 => "I32TruncSatF32S", + 0xfc01 => "I32TruncSatF32U", + 0xfc02 => "I32TruncSatF64S", + 0xfc03 => "I32TruncSatF64U", + 0xfc04 => "I64TruncSatF32S", + 0xfc05 => "I64TruncSatF32U", + 0xfc06 => "I64TruncSatF64S", + 0xfc07 => "I64TruncSatF64U", + 0xfc08 => "MemoryInit", + 0xfc09 => "DataDrop", + 0xfc0a => "MemoryCopy", + 0xfc0b => "MemoryFill", + 0xfc0c => "TableInit", + 0xfc0d => "ElemDrop", + 0xfc0e => "TableCopy", + 0xfc0f => "TableGrow", + 0xfc10 => "TableSize", + 0xfc11 => "TableFill", + 0xfc12 => "MemoryDiscard", + 0xfd00 => "V128Load", + 0xfd01 => "V128Load8x8S", + 0xfd02 => "V128Load8x8U", + 0xfd03 => "V128Load16x4S", + 0xfd04 => "V128Load16x4U", + 0xfd05 => "V128Load32x2S", + 0xfd06 => "V128Load32x2U", + 0xfd07 => "V128Load8Splat", + 0xfd08 => "V128Load16Splat", + 0xfd09 => "V128Load32Splat", + 0xfd0a => "V128Load64Splat", + 0xfd0b => "V128Store", + 0xfd0c => "V128Const", + 0xfd0d => "I8x16Shuffle", + 0xfd0e => "I8x16Swizzle", + 0xfd0f => "I8x16Splat", + 0xfd10 => "I16x8Splat", + 0xfd11 => "I32x4Splat", + 0xfd12 => "I64x2Splat", + 0xfd13 => "F32x4Splat", + 0xfd14 => "F64x2Splat", + 0xfd15 => "I8x16ExtractLaneS", + 0xfd16 => "I8x16ExtractLaneU", + 0xfd17 => "I8x16ReplaceLane", + 0xfd18 => "I16x8ExtractLaneS", + 0xfd19 => "I16x8ExtractLaneU", + 0xfd1a => "I16x8ReplaceLane", + 0xfd1b => "I32x4ExtractLane", + 0xfd1c => "I32x4ReplaceLane", + 0xfd1d => "I64x2ExtractLane", + 0xfd1e => "I64x2ReplaceLane", + 0xfd1f => "F32x4ExtractLane", + 0xfd20 => "F32x4ReplaceLane", + 0xfd21 => "F64x2ExtractLane", + 0xfd22 => "F64x2ReplaceLane", + 0xfd23 => "I8x16Eq", + 0xfd24 => "I8x16Ne", + 0xfd25 => "I8x16LtS", + 0xfd26 => "I8x16LtU", + 0xfd27 => "I8x16GtS", + 0xfd28 => "I8x16GtU", + 0xfd29 => "I8x16LeS", + 0xfd2a => "I8x16LeU", + 0xfd2b => "I8x16GeS", + 0xfd2c => "I8x16GeU", + 0xfd2d => "I16x8Eq", + 0xfd2e => "I16x8Ne", + 0xfd2f => "I16x8LtS", + 0xfd30 => "I16x8LtU", + 0xfd31 => "I16x8GtS", + 0xfd32 => "I16x8GtU", + 0xfd33 => "I16x8LeS", + 0xfd34 => "I16x8LeU", + 0xfd35 => "I16x8GeS", + 0xfd36 => "I16x8GeU", + 0xfd37 => "I32x4Eq", + 0xfd38 => "I32x4Ne", + 0xfd39 => "I32x4LtS", + 0xfd3a => "I32x4LtU", + 0xfd3b => "I32x4GtS", + 0xfd3c => "I32x4GtU", + 0xfd3d => "I32x4LeS", + 0xfd3e => "I32x4LeU", + 0xfd3f => "I32x4GeS", + 0xfd40 => "I32x4GeU", + 0xfd41 => "F32x4Eq", + 0xfd42 => "F32x4Ne", + 0xfd43 => "F32x4Lt", + 0xfd44 => "F32x4Gt", + 0xfd45 => "F32x4Le", + 0xfd46 => "F32x4Ge", + 0xfd47 => "F64x2Eq", + 0xfd48 => "F64x2Ne", + 0xfd49 => "F64x2Lt", + 0xfd4a => "F64x2Gt", + 0xfd4b => "F64x2Le", + 0xfd4c => "F64x2Ge", + 0xfd4d => "V128Not", + 0xfd4e => "V128And", + 0xfd4f => "V128AndNot", + 0xfd50 => "V128Or", + 0xfd51 => "V128Xor", + 0xfd52 => "V128Bitselect", + 0xfd53 => "V128AnyTrue", + 0xfd54 => "V128Load8Lane", + 0xfd55 => "V128Load16Lane", + 0xfd56 => "V128Load32Lane", + 0xfd57 => "V128Load64Lane", + 0xfd58 => "V128Store8Lane", + 0xfd59 => "V128Store16Lane", + 0xfd5a => "V128Store32Lane", + 0xfd5b => "V128Store64Lane", + 0xfd5c => "V128Load32Zero", + 0xfd5d => "V128Load64Zero", + 0xfd5e => "F32x4DemoteF64x2Zero", + 0xfd5f => "F64x2PromoteLowF32x4", + 0xfd60 => "I8x16Abs", + 0xfd61 => "I8x16Neg", + 0xfd62 => "I8x16Popcnt", + 0xfd63 => "I8x16AllTrue", + 0xfd64 => "I8x16Bitmask", + 0xfd65 => "I8x16NarrowI16x8S", + 0xfd66 => "I8x16NarrowI16x8U", + 0xfd67 => "F32x4Ceil", + 0xfd68 => "F32x4Floor", + 0xfd69 => "F32x4Trunc", + 0xfd6a => "F32x4Nearest", + 0xfd6b => "I8x16Shl", + 0xfd6c => "I8x16ShrS", + 0xfd6d => "I8x16ShrU", + 0xfd6e => "I8x16Add", + 0xfd6f => "I8x16AddSatS", + 0xfd70 => "I8x16AddSatU", + 0xfd71 => "I8x16Sub", + 0xfd72 => "I8x16SubSatS", + 0xfd73 => "I8x16SubSatU", + 0xfd74 => "F64x2Ceil", + 0xfd75 => "F64x2Floor", + 0xfd76 => "I8x16MinS", + 0xfd77 => "I8x16MinU", + 0xfd78 => "I8x16MaxS", + 0xfd79 => "I8x16MaxU", + 0xfd7a => "F64x2Trunc", + 0xfd7b => "I8x16AvgrU", + 0xfd7c => "I16x8ExtAddPairwiseI8x16S", + 0xfd7d => "I16x8ExtAddPairwiseI8x16U", + 0xfd7e => "I32x4ExtAddPairwiseI16x8S", + 0xfd7f => "I32x4ExtAddPairwiseI16x8U", + 0xfd80 => "I16x8Abs", + 0xfd81 => "I16x8Neg", + 0xfd82 => "I16x8Q15MulrSatS", + 0xfd83 => "I16x8AllTrue", + 0xfd84 => "I16x8Bitmask", + 0xfd85 => "I16x8NarrowI32x4S", + 0xfd86 => "I16x8NarrowI32x4U", + 0xfd87 => "I16x8ExtendLowI8x16S", + 0xfd88 => "I16x8ExtendHighI8x16S", + 0xfd89 => "I16x8ExtendLowI8x16U", + 0xfd8a => "I16x8ExtendHighI8x16U", + 0xfd8b => "I16x8Shl", + 0xfd8c => "I16x8ShrS", + 0xfd8d => "I16x8ShrU", + 0xfd8e => "I16x8Add", + 0xfd8f => "I16x8AddSatS", + 0xfd90 => "I16x8AddSatU", + 0xfd91 => "I16x8Sub", + 0xfd92 => "I16x8SubSatS", + 0xfd93 => "I16x8SubSatU", + 0xfd94 => "F64x2Nearest", + 0xfd95 => "I16x8Mul", + 0xfd96 => "I16x8MinS", + 0xfd97 => "I16x8MinU", + 0xfd98 => "I16x8MaxS", + 0xfd99 => "I16x8MaxU", + 0xfd9b => "I16x8AvgrU", + 0xfd9c => "I16x8ExtMulLowI8x16S", + 0xfd9d => "I16x8ExtMulHighI8x16S", + 0xfd9e => "I16x8ExtMulLowI8x16U", + 0xfd9f => "I16x8ExtMulHighI8x16U", + 0xfda0 => "I32x4Abs", + 0xfda2 => "I8x16RelaxedSwizzle", + 0xfda1 => "I32x4Neg", + 0xfda3 => "I32x4AllTrue", + 0xfda4 => "I32x4Bitmask", + 0xfda5 => "I32x4RelaxedTruncF32x4S", + 0xfda6 => "I32x4RelaxedTruncF32x4U", + 0xfda7 => "I32x4ExtendLowI16x8S", + 0xfda8 => "I32x4ExtendHighI16x8S", + 0xfda9 => "I32x4ExtendLowI16x8U", + 0xfdaa => "I32x4ExtendHighI16x8U", + 0xfdab => "I32x4Shl", + 0xfdac => "I32x4ShrS", + 0xfdad => "I32x4ShrU", + 0xfdae => "I32x4Add", + 0xfdaf => "F32x4RelaxedMadd", + 0xfdb0 => "F32x4RelaxedNmadd", + 0xfdb1 => "I32x4Sub", + 0xfdb2 => "I8x16RelaxedLaneselect", + 0xfdb3 => "I16x8RelaxedLaneselect", + 0xfdb4 => "F32x4RelaxedMin", + 0xfdb5 => "I32x4Mul", + 0xfdb6 => "I32x4MinS", + 0xfdb7 => "I32x4MinU", + 0xfdb8 => "I32x4MaxS", + 0xfdb9 => "I32x4MaxU", + 0xfdba => "I32x4DotI16x8S", + 0xfdbc => "I32x4ExtMulLowI16x8S", + 0xfdbd => "I32x4ExtMulHighI16x8S", + 0xfdbe => "I32x4ExtMulLowI16x8U", + 0xfdbf => "I32x4ExtMulHighI16x8U", + 0xfdc0 => "I64x2Abs", + 0xfdc1 => "I64x2Neg", + 0xfdc3 => "I64x2AllTrue", + 0xfdc4 => "I64x2Bitmask", + 0xfdc5 => "I32x4RelaxedTruncF64x2SZero", + 0xfdc6 => "I32x4RelaxedTruncF64x2UZero", + 0xfdc7 => "I64x2ExtendLowI32x4S", + 0xfdc8 => "I64x2ExtendHighI32x4S", + 0xfdc9 => "I64x2ExtendLowI32x4U", + 0xfdca => "I64x2ExtendHighI32x4U", + 0xfdcb => "I64x2Shl", + 0xfdcc => "I64x2ShrS", + 0xfdcd => "I64x2ShrU", + 0xfdce => "I64x2Add", + 0xfdcf => "F64x2RelaxedMadd", + 0xfdd0 => "F64x2RelaxedNmadd", + 0xfdd1 => "I64x2Sub", + 0xfdd2 => "I32x4RelaxedLaneselect", + 0xfdd3 => "I64x2RelaxedLaneselect", + 0xfdd4 => "F64x2RelaxedMin", + 0xfdd5 => "I64x2Mul", + 0xfdd6 => "I64x2Eq", + 0xfdd7 => "I64x2Ne", + 0xfdd8 => "I64x2LtS", + 0xfdd9 => "I64x2GtS", + 0xfdda => "I64x2LeS", + 0xfddb => "I64x2GeS", + 0xfddc => "I64x2ExtMulLowI32x4S", + 0xfddd => "I64x2ExtMulHighI32x4S", + 0xfdde => "I64x2ExtMulLowI32x4U", + 0xfddf => "I64x2ExtMulHighI32x4U", + 0xfde0 => "F32x4Abs", + 0xfde1 => "F32x4Neg", + 0xfde2 => "F32x4RelaxedMax", + 0xfde3 => "F32x4Sqrt", + 0xfde4 => "F32x4Add", + 0xfde5 => "F32x4Sub", + 0xfde6 => "F32x4Mul", + 0xfde7 => "F32x4Div", + 0xfde8 => "F32x4Min", + 0xfde9 => "F32x4Max", + 0xfdea => "F32x4PMin", + 0xfdeb => "F32x4PMax", + 0xfdec => "F64x2Abs", + 0xfded => "F64x2Neg", + 0xfdee => "F64x2RelaxedMax", + 0xfdef => "F64x2Sqrt", + 0xfdf0 => "F64x2Add", + 0xfdf1 => "F64x2Sub", + 0xfdf2 => "F64x2Mul", + 0xfdf3 => "F64x2Div", + 0xfdf4 => "F64x2Min", + 0xfdf5 => "F64x2Max", + 0xfdf6 => "F64x2PMin", + 0xfdf7 => "F64x2PMax", + 0xfdf8 => "I32x4TruncSatF32x4S", + 0xfdf9 => "I32x4TruncSatF32x4U", + 0xfdfa => "F32x4ConvertI32x4S", + 0xfdfb => "F32x4ConvertI32x4U", + 0xfdfc => "I32x4TruncSatF64x2SZero", + 0xfdfd => "I32x4TruncSatF64x2UZero", + 0xfdfe => "F64x2ConvertLowI32x4S", + 0xfdff => "F64x2ConvertLowI32x4U", + 0xfe00 => "MemoryAtomicNotify", + 0xfe01 => "MemoryAtomicWait32", + 0xfe02 => "MemoryAtomicWait64", + 0xfe03 => "AtomicFence", + 0xfe10 => "I32AtomicLoad", + 0xfe11 => "I64AtomicLoad", + 0xfe12 => "I32AtomicLoad8U", + 0xfe13 => "I32AtomicLoad16U", + 0xfe14 => "I64AtomicLoad8U", + 0xfe15 => "I64AtomicLoad16U", + 0xfe16 => "I64AtomicLoad32U", + 0xfe17 => "I32AtomicStore", + 0xfe18 => "I64AtomicStore", + 0xfe19 => "I32AtomicStore8", + 0xfe1a => "I32AtomicStore16", + 0xfe1b => "I64AtomicStore8", + 0xfe1c => "I64AtomicStore16", + 0xfe1d => "I64AtomicStore32", + 0xfe1e => "I32AtomicRmwAdd", + 0xfe1f => "I64AtomicRmwAdd", + 0xfe20 => "I32AtomicRmw8AddU", + 0xfe21 => "I32AtomicRmw16AddU", + 0xfe22 => "I64AtomicRmw8AddU", + 0xfe23 => "I64AtomicRmw16AddU", + 0xfe24 => "I64AtomicRmw32AddU", + 0xfe25 => "I32AtomicRmwSub", + 0xfe26 => "I64AtomicRmwSub", + 0xfe27 => "I32AtomicRmw8SubU", + 0xfe28 => "I32AtomicRmw16SubU", + 0xfe29 => "I64AtomicRmw8SubU", + 0xfe2a => "I64AtomicRmw16SubU", + 0xfe2b => "I64AtomicRmw32SubU", + 0xfe2c => "I32AtomicRmwAnd", + 0xfe2d => "I64AtomicRmwAnd", + 0xfe2e => "I32AtomicRmw8AndU", + 0xfe2f => "I32AtomicRmw16AndU", + 0xfe30 => "I64AtomicRmw8AndU", + 0xfe31 => "I64AtomicRmw16AndU", + 0xfe32 => "I64AtomicRmw32AndU", + 0xfe33 => "I32AtomicRmwOr", + 0xfe34 => "I64AtomicRmwOr", + 0xfe35 => "I32AtomicRmw8OrU", + 0xfe36 => "I32AtomicRmw16OrU", + 0xfe37 => "I64AtomicRmw8OrU", + 0xfe38 => "I64AtomicRmw16OrU", + 0xfe39 => "I64AtomicRmw32OrU", + 0xfe3a => "I32AtomicRmwXor", + 0xfe3b => "I64AtomicRmwXor", + 0xfe3c => "I32AtomicRmw8XorU", + 0xfe3d => "I32AtomicRmw16XorU", + 0xfe3e => "I64AtomicRmw8XorU", + 0xfe3f => "I64AtomicRmw16XorU", + 0xfe40 => "I64AtomicRmw32XorU", + 0xfe41 => "I32AtomicRmwXchg", + 0xfe42 => "I64AtomicRmwXchg", + 0xfe43 => "I32AtomicRmw8XchgU", + 0xfe44 => "I32AtomicRmw16XchgU", + 0xfe45 => "I64AtomicRmw8XchgU", + 0xfe46 => "I64AtomicRmw16XchgU", + 0xfe47 => "I64AtomicRmw32XchgU", + 0xfe48 => "I32AtomicRmwCmpxchg", + 0xfe49 => "I64AtomicRmwCmpxchg", + 0xfe4a => "I32AtomicRmw8CmpxchgU", + 0xfe4b => "I32AtomicRmw16CmpxchgU", + 0xfe4c => "I64AtomicRmw8CmpxchgU", + 0xfe4d => "I64AtomicRmw16CmpxchgU", + 0xfe4e => "I64AtomicRmw32CmpxchgU", + 0xfd111 => "I16x8RelaxedQ15mulrS", + 0xfd112 => "I16x8RelaxedDotI8x16I7x16S", + 0xfd113 => "I32x4RelaxedDotI8x16I7x16AddS", + _ => "UNKNOWN", + }; + write!(f, "{name}") + } +} + +impl<'a> From> for OperatorCode { + fn from(op: Operator) -> Self { + OperatorCode::from(&op) + } +} + +impl<'a> From<&Operator<'a>> for OperatorCode { + fn from(op: &Operator) -> Self { + use Operator as O; + + OperatorCode(match op { + O::Unreachable => 0x00, + O::Nop => 0x01, + O::Block { .. } => 0x02, + O::Loop { .. } => 0x03, + O::If { .. } => 0x04, + O::Else => 0x05, + O::Try { .. } => 0x06, + O::Catch { .. } => 0x07, + O::Throw { .. } => 0x08, + O::Rethrow { .. } => 0x09, + O::ThrowRef { .. } => 0x0A, + O::End => 0x0b, + O::Br { .. } => 0x0c, + O::BrIf { .. } => 0x0d, + O::BrTable { .. } => 0x0e, + O::Return => 0x0f, + O::Call { .. } => 0x10, + O::CallIndirect { .. } => 0x11, + O::ReturnCall { .. } => 0x12, + O::ReturnCallIndirect { .. } => 0x13, + O::CallRef { .. } => 0x14, + O::ReturnCallRef { .. } => 0x15, + O::Delegate { .. } => 0x18, + O::CatchAll => 0x19, + O::Drop => 0x1a, + O::Select => 0x1b, + O::TypedSelect { .. } => 0x1c, + O::TryTable { .. } => 0x1f, + O::LocalGet { .. } => 0x20, + O::LocalSet { .. } => 0x21, + O::LocalTee { .. } => 0x22, + O::GlobalGet { .. } => 0x23, + O::GlobalSet { .. } => 0x24, + O::TableGet { .. } => 0x25, + O::TableSet { .. } => 0x26, + O::I32Load { .. } => 0x28, + O::I64Load { .. } => 0x29, + O::F32Load { .. } => 0x2a, + O::F64Load { .. } => 0x2b, + O::I32Load8S { .. } => 0x2c, + O::I32Load8U { .. } => 0x2d, + O::I32Load16S { .. } => 0x2e, + O::I32Load16U { .. } => 0x2f, + O::I64Load8S { .. } => 0x30, + O::I64Load8U { .. } => 0x31, + O::I64Load16S { .. } => 0x32, + O::I64Load16U { .. } => 0x33, + O::I64Load32S { .. } => 0x34, + O::I64Load32U { .. } => 0x35, + O::I32Store { .. } => 0x36, + O::I64Store { .. } => 0x37, + O::F32Store { .. } => 0x38, + O::F64Store { .. } => 0x39, + O::I32Store8 { .. } => 0x3a, + O::I32Store16 { .. } => 0x3b, + O::I64Store8 { .. } => 0x3c, + O::I64Store16 { .. } => 0x3d, + O::I64Store32 { .. } => 0x3e, + O::MemorySize { .. } => 0x3f, + O::MemoryGrow { .. } => 0x40, + O::I32Const { .. } => 0x41, + O::I64Const { .. } => 0x42, + O::F32Const { .. } => 0x43, + O::F64Const { .. } => 0x44, + O::I32Eqz => 0x45, + O::I32Eq => 0x46, + O::I32Ne => 0x47, + O::I32LtS => 0x48, + O::I32LtU => 0x49, + O::I32GtS => 0x4a, + O::I32GtU => 0x4b, + O::I32LeS => 0x4c, + O::I32LeU => 0x4d, + O::I32GeS => 0x4e, + O::I32GeU => 0x4f, + O::I64Eqz => 0x50, + O::I64Eq => 0x51, + O::I64Ne => 0x52, + O::I64LtS => 0x53, + O::I64LtU => 0x54, + O::I64GtS => 0x55, + O::I64GtU => 0x56, + O::I64LeS => 0x57, + O::I64LeU => 0x58, + O::I64GeS => 0x59, + O::I64GeU => 0x5a, + O::F32Eq => 0x5b, + O::F32Ne => 0x5c, + O::F32Lt => 0x5d, + O::F32Gt => 0x5e, + O::F32Le => 0x5f, + O::F32Ge => 0x60, + O::F64Eq => 0x61, + O::F64Ne => 0x62, + O::F64Lt => 0x63, + O::F64Gt => 0x64, + O::F64Le => 0x65, + O::F64Ge => 0x66, + O::I32Clz => 0x67, + O::I32Ctz => 0x68, + O::I32Popcnt => 0x69, + O::I32Add => 0x6a, + O::I32Sub => 0x6b, + O::I32Mul => 0x6c, + O::I32DivS => 0x6d, + O::I32DivU => 0x6e, + O::I32RemS => 0x6f, + O::I32RemU => 0x70, + O::I32And => 0x71, + O::I32Or => 0x72, + O::I32Xor => 0x73, + O::I32Shl => 0x74, + O::I32ShrS => 0x75, + O::I32ShrU => 0x76, + O::I32Rotl => 0x77, + O::I32Rotr => 0x78, + O::I64Clz => 0x79, + O::I64Ctz => 0x7a, + O::I64Popcnt => 0x7b, + O::I64Add => 0x7c, + O::I64Sub => 0x7d, + O::I64Mul => 0x7e, + O::I64DivS => 0x7f, + O::I64DivU => 0x80, + O::I64RemS => 0x81, + O::I64RemU => 0x82, + O::I64And => 0x83, + O::I64Or => 0x84, + O::I64Xor => 0x85, + O::I64Shl => 0x86, + O::I64ShrS => 0x87, + O::I64ShrU => 0x88, + O::I64Rotl => 0x89, + O::I64Rotr => 0x8a, + O::F32Abs => 0x8b, + O::F32Neg => 0x8c, + O::F32Ceil => 0x8d, + O::F32Floor => 0x8e, + O::F32Trunc => 0x8f, + O::F32Nearest => 0x90, + O::F32Sqrt => 0x91, + O::F32Add => 0x92, + O::F32Sub => 0x93, + O::F32Mul => 0x94, + O::F32Div => 0x95, + O::F32Min => 0x96, + O::F32Max => 0x97, + O::F32Copysign => 0x98, + O::F64Abs => 0x99, + O::F64Neg => 0x9a, + O::F64Ceil => 0x9b, + O::F64Floor => 0x9c, + O::F64Trunc => 0x9d, + O::F64Nearest => 0x9e, + O::F64Sqrt => 0x9f, + O::F64Add => 0xa0, + O::F64Sub => 0xa1, + O::F64Mul => 0xa2, + O::F64Div => 0xa3, + O::F64Min => 0xa4, + O::F64Max => 0xa5, + O::F64Copysign => 0xa6, + O::I32WrapI64 => 0xa7, + O::I32TruncF32S => 0xa8, + O::I32TruncF32U => 0xa9, + O::I32TruncF64S => 0xaa, + O::I32TruncF64U => 0xab, + O::I64ExtendI32S => 0xac, + O::I64ExtendI32U => 0xad, + O::I64TruncF32S => 0xae, + O::I64TruncF32U => 0xaf, + O::I64TruncF64S => 0xb0, + O::I64TruncF64U => 0xb1, + O::F32ConvertI32S => 0xb2, + O::F32ConvertI32U => 0xb3, + O::F32ConvertI64S => 0xb4, + O::F32ConvertI64U => 0xb5, + O::F32DemoteF64 => 0xb6, + O::F64ConvertI32S => 0xb7, + O::F64ConvertI32U => 0xb8, + O::F64ConvertI64S => 0xb9, + O::F64ConvertI64U => 0xba, + O::F64PromoteF32 => 0xbb, + O::I32ReinterpretF32 => 0xbc, + O::I64ReinterpretF64 => 0xbd, + O::F32ReinterpretI32 => 0xbe, + O::F64ReinterpretI64 => 0xbf, + O::I32Extend8S => 0xc0, + O::I32Extend16S => 0xc1, + O::I64Extend8S => 0xc2, + O::I64Extend16S => 0xc3, + O::I64Extend32S => 0xc4, + O::RefNull { .. } => 0xd0, + O::RefIsNull => 0xd1, + O::RefFunc { .. } => 0xd2, + O::RefAsNonNull => 0xd3, + O::BrOnNull { .. } => 0xd4, + O::RefEq { .. } => 0xd5, + O::BrOnNonNull { .. } => 0xd6, + O::StructNew { .. } => 0xfb00, + O::StructNewDefault { .. } => 0xfb01, + O::StructGet { .. } => 0xfb02, + O::StructGetS { .. } => 0xfb03, + O::StructGetU { .. } => 0xfb04, + O::StructSet { .. } => 0xfb05, + O::ArrayNew { .. } => 0xfb06, + O::ArrayNewDefault { .. } => 0xfb07, + O::ArrayNewFixed { .. } => 0xfb08, + O::ArrayNewData { .. } => 0xfb09, + O::ArrayNewElem { .. } => 0xfb0a, + O::ArrayGet { .. } => 0xfb0b, + O::ArrayGetS { .. } => 0xfb0c, + O::ArrayGetU { .. } => 0xfb0d, + O::ArraySet { .. } => 0xfb0e, + O::ArrayLen { .. } => 0xfb0f, + O::ArrayFill { .. } => 0xfb10, + O::ArrayCopy { .. } => 0xfb11, + O::ArrayInitData { .. } => 0xfb12, + O::ArrayInitElem { .. } => 0xfb13, + O::RefTestNonNull { .. } => 0xfb14, + O::RefTestNullable { .. } => 0xfb15, + O::RefCastNonNull { .. } => 0xfb16, + O::RefCastNullable { .. } => 0xfb17, + O::BrOnCast { .. } => 0xfb18, + O::BrOnCastFail { .. } => 0xfb19, + O::AnyConvertExtern => 0xfb1a, + O::ExternConvertAny => 0xfb1b, + O::RefI31 { .. } => 0xfb1c, + O::I31GetS => 0xfb1d, + O::I31GetU => 0xfb1e, + O::I32TruncSatF32S => 0xfc00, + O::I32TruncSatF32U => 0xfc01, + O::I32TruncSatF64S => 0xfc02, + O::I32TruncSatF64U => 0xfc03, + O::I64TruncSatF32S => 0xfc04, + O::I64TruncSatF32U => 0xfc05, + O::I64TruncSatF64S => 0xfc06, + O::I64TruncSatF64U => 0xfc07, + O::MemoryInit { .. } => 0xfc08, + O::DataDrop { .. } => 0xfc09, + O::MemoryCopy { .. } => 0xfc0a, + O::MemoryFill { .. } => 0xfc0b, + O::TableInit { .. } => 0xfc0c, + O::ElemDrop { .. } => 0xfc0d, + O::TableCopy { .. } => 0xfc0e, + O::TableGrow { .. } => 0xfc0f, + O::TableSize { .. } => 0xfc10, + O::TableFill { .. } => 0xfc11, + O::MemoryDiscard { .. } => 0xfc12, + O::V128Load { .. } => 0xfd00, + O::V128Load8x8S { .. } => 0xfd01, + O::V128Load8x8U { .. } => 0xfd02, + O::V128Load16x4S { .. } => 0xfd03, + O::V128Load16x4U { .. } => 0xfd04, + O::V128Load32x2S { .. } => 0xfd05, + O::V128Load32x2U { .. } => 0xfd06, + O::V128Load8Splat { .. } => 0xfd07, + O::V128Load16Splat { .. } => 0xfd08, + O::V128Load32Splat { .. } => 0xfd09, + O::V128Load64Splat { .. } => 0xfd0a, + O::V128Store { .. } => 0xfd0b, + O::V128Const { .. } => 0xfd0c, + O::I8x16Shuffle { .. } => 0xfd0d, + O::I8x16Swizzle => 0xfd0e, + O::I8x16Splat => 0xfd0f, + O::I16x8Splat => 0xfd10, + O::I32x4Splat => 0xfd11, + O::I64x2Splat => 0xfd12, + O::F32x4Splat => 0xfd13, + O::F64x2Splat => 0xfd14, + O::I8x16ExtractLaneS { .. } => 0xfd15, + O::I8x16ExtractLaneU { .. } => 0xfd16, + O::I8x16ReplaceLane { .. } => 0xfd17, + O::I16x8ExtractLaneS { .. } => 0xfd18, + O::I16x8ExtractLaneU { .. } => 0xfd19, + O::I16x8ReplaceLane { .. } => 0xfd1a, + O::I32x4ExtractLane { .. } => 0xfd1b, + O::I32x4ReplaceLane { .. } => 0xfd1c, + O::I64x2ExtractLane { .. } => 0xfd1d, + O::I64x2ReplaceLane { .. } => 0xfd1e, + O::F32x4ExtractLane { .. } => 0xfd1f, + O::F32x4ReplaceLane { .. } => 0xfd20, + O::F64x2ExtractLane { .. } => 0xfd21, + O::F64x2ReplaceLane { .. } => 0xfd22, + O::I8x16Eq => 0xfd23, + O::I8x16Ne => 0xfd24, + O::I8x16LtS => 0xfd25, + O::I8x16LtU => 0xfd26, + O::I8x16GtS => 0xfd27, + O::I8x16GtU => 0xfd28, + O::I8x16LeS => 0xfd29, + O::I8x16LeU => 0xfd2a, + O::I8x16GeS => 0xfd2b, + O::I8x16GeU => 0xfd2c, + O::I16x8Eq => 0xfd2d, + O::I16x8Ne => 0xfd2e, + O::I16x8LtS => 0xfd2f, + O::I16x8LtU => 0xfd30, + O::I16x8GtS => 0xfd31, + O::I16x8GtU => 0xfd32, + O::I16x8LeS => 0xfd33, + O::I16x8LeU => 0xfd34, + O::I16x8GeS => 0xfd35, + O::I16x8GeU => 0xfd36, + O::I32x4Eq => 0xfd37, + O::I32x4Ne => 0xfd38, + O::I32x4LtS => 0xfd39, + O::I32x4LtU => 0xfd3a, + O::I32x4GtS => 0xfd3b, + O::I32x4GtU => 0xfd3c, + O::I32x4LeS => 0xfd3d, + O::I32x4LeU => 0xfd3e, + O::I32x4GeS => 0xfd3f, + O::I32x4GeU => 0xfd40, + O::F32x4Eq => 0xfd41, + O::F32x4Ne => 0xfd42, + O::F32x4Lt => 0xfd43, + O::F32x4Gt => 0xfd44, + O::F32x4Le => 0xfd45, + O::F32x4Ge => 0xfd46, + O::F64x2Eq => 0xfd47, + O::F64x2Ne => 0xfd48, + O::F64x2Lt => 0xfd49, + O::F64x2Gt => 0xfd4a, + O::F64x2Le => 0xfd4b, + O::F64x2Ge => 0xfd4c, + O::V128Not => 0xfd4d, + O::V128And => 0xfd4e, + O::V128AndNot => 0xfd4f, + O::V128Or => 0xfd50, + O::V128Xor => 0xfd51, + O::V128Bitselect => 0xfd52, + O::V128AnyTrue => 0xfd53, + O::V128Load8Lane { .. } => 0xfd54, + O::V128Load16Lane { .. } => 0xfd55, + O::V128Load32Lane { .. } => 0xfd56, + O::V128Load64Lane { .. } => 0xfd57, + O::V128Store8Lane { .. } => 0xfd58, + O::V128Store16Lane { .. } => 0xfd59, + O::V128Store32Lane { .. } => 0xfd5a, + O::V128Store64Lane { .. } => 0xfd5b, + O::V128Load32Zero { .. } => 0xfd5c, + O::V128Load64Zero { .. } => 0xfd5d, + O::F32x4DemoteF64x2Zero => 0xfd5e, + O::F64x2PromoteLowF32x4 => 0xfd5f, + O::I8x16Abs => 0xfd60, + O::I8x16Neg => 0xfd61, + O::I8x16Popcnt => 0xfd62, + O::I8x16AllTrue => 0xfd63, + O::I8x16Bitmask => 0xfd64, + O::I8x16NarrowI16x8S => 0xfd65, + O::I8x16NarrowI16x8U => 0xfd66, + O::F32x4Ceil => 0xfd67, + O::F32x4Floor => 0xfd68, + O::F32x4Trunc => 0xfd69, + O::F32x4Nearest => 0xfd6a, + O::I8x16Shl => 0xfd6b, + O::I8x16ShrS => 0xfd6c, + O::I8x16ShrU => 0xfd6d, + O::I8x16Add => 0xfd6e, + O::I8x16AddSatS => 0xfd6f, + O::I8x16AddSatU => 0xfd70, + O::I8x16Sub => 0xfd71, + O::I8x16SubSatS => 0xfd72, + O::I8x16SubSatU => 0xfd73, + O::F64x2Ceil => 0xfd74, + O::F64x2Floor => 0xfd75, + O::I8x16MinS => 0xfd76, + O::I8x16MinU => 0xfd77, + O::I8x16MaxS => 0xfd78, + O::I8x16MaxU => 0xfd79, + O::F64x2Trunc => 0xfd7a, + O::I8x16AvgrU => 0xfd7b, + O::I16x8ExtAddPairwiseI8x16S => 0xfd7c, + O::I16x8ExtAddPairwiseI8x16U => 0xfd7d, + O::I32x4ExtAddPairwiseI16x8S => 0xfd7e, + O::I32x4ExtAddPairwiseI16x8U => 0xfd7f, + O::I16x8Abs => 0xfd80, + O::I16x8Neg => 0xfd81, + O::I16x8Q15MulrSatS => 0xfd82, + O::I16x8AllTrue => 0xfd83, + O::I16x8Bitmask => 0xfd84, + O::I16x8NarrowI32x4S => 0xfd85, + O::I16x8NarrowI32x4U => 0xfd86, + O::I16x8ExtendLowI8x16S => 0xfd87, + O::I16x8ExtendHighI8x16S => 0xfd88, + O::I16x8ExtendLowI8x16U => 0xfd89, + O::I16x8ExtendHighI8x16U => 0xfd8a, + O::I16x8Shl => 0xfd8b, + O::I16x8ShrS => 0xfd8c, + O::I16x8ShrU => 0xfd8d, + O::I16x8Add => 0xfd8e, + O::I16x8AddSatS => 0xfd8f, + O::I16x8AddSatU => 0xfd90, + O::I16x8Sub => 0xfd91, + O::I16x8SubSatS => 0xfd92, + O::I16x8SubSatU => 0xfd93, + O::F64x2Nearest => 0xfd94, + O::I16x8Mul => 0xfd95, + O::I16x8MinS => 0xfd96, + O::I16x8MinU => 0xfd97, + O::I16x8MaxS => 0xfd98, + O::I16x8MaxU => 0xfd99, + O::I16x8AvgrU => 0xfd9b, + O::I16x8ExtMulLowI8x16S => 0xfd9c, + O::I16x8ExtMulHighI8x16S => 0xfd9d, + O::I16x8ExtMulLowI8x16U => 0xfd9e, + O::I16x8ExtMulHighI8x16U => 0xfd9f, + O::I32x4Abs => 0xfda0, + O::I8x16RelaxedSwizzle => 0xfda2, + O::I32x4Neg => 0xfda1, + O::I32x4AllTrue => 0xfda3, + O::I32x4Bitmask => 0xfda4, + O::I32x4RelaxedTruncF32x4S => 0xfda5, + O::I32x4RelaxedTruncF32x4U => 0xfda6, + O::I32x4ExtendLowI16x8S => 0xfda7, + O::I32x4ExtendHighI16x8S => 0xfda8, + O::I32x4ExtendLowI16x8U => 0xfda9, + O::I32x4ExtendHighI16x8U => 0xfdaa, + O::I32x4Shl => 0xfdab, + O::I32x4ShrS => 0xfdac, + O::I32x4ShrU => 0xfdad, + O::I32x4Add => 0xfdae, + O::F32x4RelaxedMadd => 0xfdaf, + O::F32x4RelaxedNmadd => 0xfdb0, + O::I32x4Sub => 0xfdb1, + O::I8x16RelaxedLaneselect => 0xfdb2, + O::I16x8RelaxedLaneselect => 0xfdb3, + O::F32x4RelaxedMin => 0xfdb4, + O::I32x4Mul => 0xfdb5, + O::I32x4MinS => 0xfdb6, + O::I32x4MinU => 0xfdb7, + O::I32x4MaxS => 0xfdb8, + O::I32x4MaxU => 0xfdb9, + O::I32x4DotI16x8S => 0xfdba, + O::I32x4ExtMulLowI16x8S => 0xfdbc, + O::I32x4ExtMulHighI16x8S => 0xfdbd, + O::I32x4ExtMulLowI16x8U => 0xfdbe, + O::I32x4ExtMulHighI16x8U => 0xfdbf, + O::I64x2Abs => 0xfdc0, + O::I64x2Neg => 0xfdc1, + O::I64x2AllTrue => 0xfdc3, + O::I64x2Bitmask => 0xfdc4, + O::I32x4RelaxedTruncF64x2SZero => 0xfdc5, + O::I32x4RelaxedTruncF64x2UZero => 0xfdc6, + O::I64x2ExtendLowI32x4S => 0xfdc7, + O::I64x2ExtendHighI32x4S => 0xfdc8, + O::I64x2ExtendLowI32x4U => 0xfdc9, + O::I64x2ExtendHighI32x4U => 0xfdca, + O::I64x2Shl => 0xfdcb, + O::I64x2ShrS => 0xfdcc, + O::I64x2ShrU => 0xfdcd, + O::I64x2Add => 0xfdce, + O::F64x2RelaxedMadd => 0xfdcf, + O::F64x2RelaxedNmadd => 0xfdd0, + O::I64x2Sub => 0xfdd1, + O::I32x4RelaxedLaneselect => 0xfdd2, + O::I64x2RelaxedLaneselect => 0xfdd3, + O::F64x2RelaxedMin => 0xfdd4, + O::I64x2Mul => 0xfdd5, + O::I64x2Eq => 0xfdd6, + O::I64x2Ne => 0xfdd7, + O::I64x2LtS => 0xfdd8, + O::I64x2GtS => 0xfdd9, + O::I64x2LeS => 0xfdda, + O::I64x2GeS => 0xfddb, + O::I64x2ExtMulLowI32x4S => 0xfddc, + O::I64x2ExtMulHighI32x4S => 0xfddd, + O::I64x2ExtMulLowI32x4U => 0xfdde, + O::I64x2ExtMulHighI32x4U => 0xfddf, + O::F32x4Abs => 0xfde0, + O::F32x4Neg => 0xfde1, + O::F32x4RelaxedMax => 0xfde2, + O::F32x4Sqrt => 0xfde3, + O::F32x4Add => 0xfde4, + O::F32x4Sub => 0xfde5, + O::F32x4Mul => 0xfde6, + O::F32x4Div => 0xfde7, + O::F32x4Min => 0xfde8, + O::F32x4Max => 0xfde9, + O::F32x4PMin => 0xfdea, + O::F32x4PMax => 0xfdeb, + O::F64x2Abs => 0xfdec, + O::F64x2Neg => 0xfded, + O::F64x2RelaxedMax => 0xfdee, + O::F64x2Sqrt => 0xfdef, + O::F64x2Add => 0xfdf0, + O::F64x2Sub => 0xfdf1, + O::F64x2Mul => 0xfdf2, + O::F64x2Div => 0xfdf3, + O::F64x2Min => 0xfdf4, + O::F64x2Max => 0xfdf5, + O::F64x2PMin => 0xfdf6, + O::F64x2PMax => 0xfdf7, + O::I32x4TruncSatF32x4S => 0xfdf8, + O::I32x4TruncSatF32x4U => 0xfdf9, + O::F32x4ConvertI32x4S => 0xfdfa, + O::F32x4ConvertI32x4U => 0xfdfb, + O::I32x4TruncSatF64x2SZero => 0xfdfc, + O::I32x4TruncSatF64x2UZero => 0xfdfd, + O::F64x2ConvertLowI32x4S => 0xfdfe, + O::F64x2ConvertLowI32x4U => 0xfdff, + O::MemoryAtomicNotify { .. } => 0xfe00, + O::MemoryAtomicWait32 { .. } => 0xfe01, + O::MemoryAtomicWait64 { .. } => 0xfe02, + O::AtomicFence { .. } => 0xfe03, + O::I32AtomicLoad { .. } => 0xfe10, + O::I64AtomicLoad { .. } => 0xfe11, + O::I32AtomicLoad8U { .. } => 0xfe12, + O::I32AtomicLoad16U { .. } => 0xfe13, + O::I64AtomicLoad8U { .. } => 0xfe14, + O::I64AtomicLoad16U { .. } => 0xfe15, + O::I64AtomicLoad32U { .. } => 0xfe16, + O::I32AtomicStore { .. } => 0xfe17, + O::I64AtomicStore { .. } => 0xfe18, + O::I32AtomicStore8 { .. } => 0xfe19, + O::I32AtomicStore16 { .. } => 0xfe1a, + O::I64AtomicStore8 { .. } => 0xfe1b, + O::I64AtomicStore16 { .. } => 0xfe1c, + O::I64AtomicStore32 { .. } => 0xfe1d, + O::I32AtomicRmwAdd { .. } => 0xfe1e, + O::I64AtomicRmwAdd { .. } => 0xfe1f, + O::I32AtomicRmw8AddU { .. } => 0xfe20, + O::I32AtomicRmw16AddU { .. } => 0xfe21, + O::I64AtomicRmw8AddU { .. } => 0xfe22, + O::I64AtomicRmw16AddU { .. } => 0xfe23, + O::I64AtomicRmw32AddU { .. } => 0xfe24, + O::I32AtomicRmwSub { .. } => 0xfe25, + O::I64AtomicRmwSub { .. } => 0xfe26, + O::I32AtomicRmw8SubU { .. } => 0xfe27, + O::I32AtomicRmw16SubU { .. } => 0xfe28, + O::I64AtomicRmw8SubU { .. } => 0xfe29, + O::I64AtomicRmw16SubU { .. } => 0xfe2a, + O::I64AtomicRmw32SubU { .. } => 0xfe2b, + O::I32AtomicRmwAnd { .. } => 0xfe2c, + O::I64AtomicRmwAnd { .. } => 0xfe2d, + O::I32AtomicRmw8AndU { .. } => 0xfe2e, + O::I32AtomicRmw16AndU { .. } => 0xfe2f, + O::I64AtomicRmw8AndU { .. } => 0xfe30, + O::I64AtomicRmw16AndU { .. } => 0xfe31, + O::I64AtomicRmw32AndU { .. } => 0xfe32, + O::I32AtomicRmwOr { .. } => 0xfe33, + O::I64AtomicRmwOr { .. } => 0xfe34, + O::I32AtomicRmw8OrU { .. } => 0xfe35, + O::I32AtomicRmw16OrU { .. } => 0xfe36, + O::I64AtomicRmw8OrU { .. } => 0xfe37, + O::I64AtomicRmw16OrU { .. } => 0xfe38, + O::I64AtomicRmw32OrU { .. } => 0xfe39, + O::I32AtomicRmwXor { .. } => 0xfe3a, + O::I64AtomicRmwXor { .. } => 0xfe3b, + O::I32AtomicRmw8XorU { .. } => 0xfe3c, + O::I32AtomicRmw16XorU { .. } => 0xfe3d, + O::I64AtomicRmw8XorU { .. } => 0xfe3e, + O::I64AtomicRmw16XorU { .. } => 0xfe3f, + O::I64AtomicRmw32XorU { .. } => 0xfe40, + O::I32AtomicRmwXchg { .. } => 0xfe41, + O::I64AtomicRmwXchg { .. } => 0xfe42, + O::I32AtomicRmw8XchgU { .. } => 0xfe43, + O::I32AtomicRmw16XchgU { .. } => 0xfe44, + O::I64AtomicRmw8XchgU { .. } => 0xfe45, + O::I64AtomicRmw16XchgU { .. } => 0xfe46, + O::I64AtomicRmw32XchgU { .. } => 0xfe47, + O::I32AtomicRmwCmpxchg { .. } => 0xfe48, + O::I64AtomicRmwCmpxchg { .. } => 0xfe49, + O::I32AtomicRmw8CmpxchgU { .. } => 0xfe4a, + O::I32AtomicRmw16CmpxchgU { .. } => 0xfe4b, + O::I64AtomicRmw8CmpxchgU { .. } => 0xfe4c, + O::I64AtomicRmw16CmpxchgU { .. } => 0xfe4d, + O::I64AtomicRmw32CmpxchgU { .. } => 0xfe4e, + O::I16x8RelaxedQ15mulrS { .. } => 0xfd111, + O::I16x8RelaxedDotI8x16I7x16S { .. } => 0xfd112, + O::I32x4RelaxedDotI8x16I7x16AddS { .. } => 0xfd113, + }) + } +} + +pub trait OperatorInfo { + fn ends_basic_block(&self) -> bool; + fn code(&self) -> OperatorCode; +} + +impl OperatorInfo for Operator<'_> { + fn ends_basic_block(&self) -> bool { + use Operator::*; + + macro_rules! dot { + ($first:ident $(,$opcode:ident)*) => { + $first { .. } $(| $opcode { .. })* + }; + } + + matches!( + self, + End | Else | Return | dot!(Loop, Br, BrTable, BrIf, If, Call, CallIndirect) + ) + } + + fn code(&self) -> OperatorCode { + self.into() + } +} diff --git a/arbitrator/arbutil/src/pricing.rs b/arbitrator/arbutil/src/pricing.rs new file mode 100644 index 000000000..4614b02a2 --- /dev/null +++ b/arbitrator/arbutil/src/pricing.rs @@ -0,0 +1,20 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +/// For hostios that may return something. +pub const HOSTIO_INK: u64 = 8400; + +/// For hostios that include pointers. +pub const PTR_INK: u64 = 13440 - HOSTIO_INK; + +/// For hostios that involve an API cost. +pub const EVM_API_INK: u64 = 59673; + +/// For hostios that involve a div or mod. +pub const DIV_INK: u64 = 20000; + +/// For hostios that involve a mulmod. +pub const MUL_MOD_INK: u64 = 24100; + +/// For hostios that involve an addmod. +pub const ADD_MOD_INK: u64 = 21000; diff --git a/arbitrator/arbutil/src/types.rs b/arbitrator/arbutil/src/types.rs index 9cc67cec7..0c214953c 100644 --- a/arbitrator/arbutil/src/types.rs +++ b/arbitrator/arbutil/src/types.rs @@ -2,6 +2,13 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use num_enum::{IntoPrimitive, TryFromPrimitive}; +use ruint2::Uint; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Borrow, + fmt, + ops::{Deref, DerefMut}, +}; // These values must be kept in sync with `arbutil/preimage_type.go`, // and the if statement in `contracts/src/osp/OneStepProverHostIo.sol` (search for "UNKNOWN_PREIMAGE_TYPE"). @@ -15,3 +22,231 @@ pub enum PreimageType { EthVersionedHash, EigenDAHash, } + +/// cbindgen:field-names=[bytes] +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(C)] +pub struct Bytes32(pub [u8; 32]); + +impl Deref for Bytes32 { + type Target = [u8; 32]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Bytes32 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl AsRef<[u8]> for Bytes32 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Borrow<[u8]> for Bytes32 { + fn borrow(&self) -> &[u8] { + &self.0 + } +} + +impl From<[u8; 32]> for Bytes32 { + fn from(x: [u8; 32]) -> Self { + Self(x) + } +} + +impl From for Bytes32 { + fn from(x: u32) -> Self { + let mut b = [0u8; 32]; + b[(32 - 4)..].copy_from_slice(&x.to_be_bytes()); + Self(b) + } +} + +impl From for Bytes32 { + fn from(x: u64) -> Self { + let mut b = [0u8; 32]; + b[(32 - 8)..].copy_from_slice(&x.to_be_bytes()); + Self(b) + } +} + +impl From for Bytes32 { + fn from(x: usize) -> Self { + let mut b = [0u8; 32]; + b[(32 - (usize::BITS as usize / 8))..].copy_from_slice(&x.to_be_bytes()); + Self(b) + } +} + +impl TryFrom<&[u8]> for Bytes32 { + type Error = std::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + let value: [u8; 32] = value.try_into()?; + Ok(Self(value)) + } +} + +impl TryFrom> for Bytes32 { + type Error = std::array::TryFromSliceError; + + fn try_from(value: Vec) -> Result { + Self::try_from(value.as_slice()) + } +} + +impl IntoIterator for Bytes32 { + type Item = u8; + type IntoIter = std::array::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIterator::into_iter(self.0) + } +} + +impl fmt::Display for Bytes32 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self)) + } +} + +impl fmt::Debug for Bytes32 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self)) + } +} + +type GenericBytes32 = digest::generic_array::GenericArray; + +impl From for Bytes32 { + fn from(x: GenericBytes32) -> Self { + <[u8; 32]>::from(x).into() + } +} + +type U256 = Uint<256, 4>; + +impl From for U256 { + fn from(value: Bytes32) -> Self { + U256::from_be_bytes(value.0) + } +} + +impl From for Bytes32 { + fn from(value: U256) -> Self { + Self(value.to_be_bytes()) + } +} + +/// cbindgen:field-names=[bytes] +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(C)] +pub struct Bytes20(pub [u8; 20]); + +impl Deref for Bytes20 { + type Target = [u8; 20]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Bytes20 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl AsRef<[u8]> for Bytes20 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Borrow<[u8]> for Bytes20 { + fn borrow(&self) -> &[u8] { + &self.0 + } +} + +impl From<[u8; 20]> for Bytes20 { + fn from(x: [u8; 20]) -> Self { + Self(x) + } +} + +impl From for Bytes20 { + fn from(x: u32) -> Self { + let mut b = [0u8; 20]; + b[(20 - 4)..].copy_from_slice(&x.to_be_bytes()); + Self(b) + } +} + +impl From for Bytes20 { + fn from(x: u64) -> Self { + let mut b = [0u8; 20]; + b[(20 - 8)..].copy_from_slice(&x.to_be_bytes()); + Self(b) + } +} + +impl From for Bytes20 { + fn from(x: usize) -> Self { + let mut b = [0u8; 20]; + b[(32 - (usize::BITS as usize / 8))..].copy_from_slice(&x.to_be_bytes()); + Self(b) + } +} + +impl TryFrom<&[u8]> for Bytes20 { + type Error = std::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + let value: [u8; 20] = value.try_into()?; + Ok(Self(value)) + } +} + +impl TryFrom> for Bytes20 { + type Error = std::array::TryFromSliceError; + + fn try_from(value: Vec) -> Result { + Self::try_from(value.as_slice()) + } +} + +impl IntoIterator for Bytes20 { + type Item = u8; + type IntoIter = std::array::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIterator::into_iter(self.0) + } +} + +impl fmt::Display for Bytes20 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self)) + } +} + +impl fmt::Debug for Bytes20 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self)) + } +} + +type GenericBytes20 = digest::generic_array::GenericArray; + +impl From for Bytes20 { + fn from(x: GenericBytes20) -> Self { + <[u8; 20]>::from(x).into() + } +} diff --git a/arbitrator/brotli/Cargo.toml b/arbitrator/brotli/Cargo.toml new file mode 100644 index 000000000..7dba0ffdd --- /dev/null +++ b/arbitrator/brotli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "brotli" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +lazy_static.workspace = true +num_enum.workspace = true +wasmer = { path = "../tools/wasmer/lib/api", optional = true } +wee_alloc.workspace = true + +[lib] +crate-type = ["lib"] + +[features] +wasmer_traits = ["dep:wasmer"] diff --git a/arbitrator/brotli/build.rs b/arbitrator/brotli/build.rs new file mode 100644 index 000000000..8ed54397b --- /dev/null +++ b/arbitrator/brotli/build.rs @@ -0,0 +1,18 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use std::env; + +fn main() { + let target_arch = env::var("TARGET").unwrap(); + + if target_arch.contains("wasm32") { + println!("cargo:rustc-link-search=../../target/lib-wasm/"); + } else { + println!("cargo:rustc-link-search=../target/lib/"); + println!("cargo:rustc-link-search=../../target/lib/"); + } + println!("cargo:rustc-link-lib=static=brotlienc-static"); + println!("cargo:rustc-link-lib=static=brotlidec-static"); + println!("cargo:rustc-link-lib=static=brotlicommon-static"); +} diff --git a/arbitrator/brotli/fuzz/.gitignore b/arbitrator/brotli/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/arbitrator/brotli/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/arbitrator/brotli/fuzz/Cargo.toml b/arbitrator/brotli/fuzz/Cargo.toml new file mode 100644 index 000000000..2dc699334 --- /dev/null +++ b/arbitrator/brotli/fuzz/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "brotli-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +hex = "0.4.3" + +[dependencies.brotli] +path = ".." + +[[bin]] +name = "compress" +path = "fuzz_targets/compress.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "decompress" +path = "fuzz_targets/decompress.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "round-trip" +path = "fuzz_targets/round_trip.rs" +test = false +doc = false +bench = false diff --git a/arbitrator/brotli/fuzz/README b/arbitrator/brotli/fuzz/README new file mode 100644 index 000000000..e00f4c343 --- /dev/null +++ b/arbitrator/brotli/fuzz/README @@ -0,0 +1,13 @@ + +Fuzzing for brotli. You'll need `cargo-fuzz`. Install it with `cargo install +cargo-fuzz`. You'll also need to use the Rust nightly compiler - `rustup +default nightly`. + +Then you can fuzz with +```bash +cargo +nightly fuzz run compress -- -max_len=262144 +``` +or +```bash +cargo +nightly fuzz run decompress -- -max_len=262144 +``` diff --git a/arbitrator/brotli/fuzz/fuzz_targets/compress.rs b/arbitrator/brotli/fuzz/fuzz_targets/compress.rs new file mode 100644 index 000000000..6141ede76 --- /dev/null +++ b/arbitrator/brotli/fuzz/fuzz_targets/compress.rs @@ -0,0 +1,18 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|arg: (&[u8], u32, u32)| { + let data = arg.0; + let quality = arg.1; + let window = arg.2; + let _ = brotli::compress( + data, + 1 + quality % 12, + 10 + window % 15, + brotli::Dictionary::StylusProgram, + ); +}); diff --git a/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs new file mode 100644 index 000000000..dd36d6483 --- /dev/null +++ b/arbitrator/brotli/fuzz/fuzz_targets/decompress.rs @@ -0,0 +1,28 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![no_main] + +use brotli::Dictionary; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let mut data = data; + let dict = Dictionary::StylusProgram; + + let mut space = 0_u32; + if data.len() >= 4 { + space = u32::from_le_bytes(data[..4].try_into().unwrap()); + data = &data[4..]; + } + + let mut array = Vec::with_capacity(space as usize % 65536); + let array = &mut array.spare_capacity_mut(); + + let plain = brotli::decompress(data, dict); + let fixed = brotli::decompress_fixed(data, array, dict); + + if let Ok(fixed) = fixed { + assert_eq!(fixed.len(), plain.unwrap().len()); // fixed succeeding implies both do + } +}); diff --git a/arbitrator/brotli/fuzz/fuzz_targets/round_trip.rs b/arbitrator/brotli/fuzz/fuzz_targets/round_trip.rs new file mode 100644 index 000000000..2f47584cf --- /dev/null +++ b/arbitrator/brotli/fuzz/fuzz_targets/round_trip.rs @@ -0,0 +1,23 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![no_main] + +use brotli::Dictionary; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let dict = Dictionary::Empty; + let split = data + .first() + .map(|x| *x as usize) + .unwrap_or_default() + .min(data.len()); + + let (header, data) = data.split_at(split); + let image = brotli::compress_into(data, header.to_owned(), 0, 22, dict).unwrap(); + let prior = brotli::decompress(&image[split..], dict).unwrap(); + + assert_eq!(&image[..split], header); + assert_eq!(prior, data); +}); diff --git a/arbitrator/brotli/src/cgo.rs b/arbitrator/brotli/src/cgo.rs new file mode 100644 index 000000000..3581d024f --- /dev/null +++ b/arbitrator/brotli/src/cgo.rs @@ -0,0 +1,66 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{BrotliStatus, Dictionary, DEFAULT_WINDOW_SIZE}; +use core::{mem::MaybeUninit, slice}; + +/// Mechanism for passing data between Go and Rust where Rust can specify the initialized length. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct BrotliBuffer { + /// Points to data owned by Go. + ptr: *mut u8, + /// The length in bytes. Rust may mutate this value to indicate the number of bytes initialized. + len: *mut usize, +} + +impl BrotliBuffer { + /// Interprets the underlying Go data as a Rust slice. + fn as_slice(&self) -> &[u8] { + let len = unsafe { *self.len }; + if len == 0 { + return &[]; + } + unsafe { slice::from_raw_parts(self.ptr, len) } + } + + /// Interprets the underlying Go data as a Rust slice of uninitialized data. + fn as_uninit(&mut self) -> &mut [MaybeUninit] { + let len = unsafe { *self.len }; + if len == 0 { + return &mut []; + } + unsafe { slice::from_raw_parts_mut(self.ptr as _, len) } + } +} + +/// Brotli compresses the given Go data into a buffer of limited capacity. +#[no_mangle] +pub extern "C" fn brotli_compress( + input: BrotliBuffer, + mut output: BrotliBuffer, + dictionary: Dictionary, + level: u32, +) -> BrotliStatus { + let window = DEFAULT_WINDOW_SIZE; + let buffer = output.as_uninit(); + match crate::compress_fixed(input.as_slice(), buffer, level, window, dictionary) { + Ok(slice) => unsafe { *output.len = slice.len() }, + Err(status) => return status, + } + BrotliStatus::Success +} + +/// Brotli decompresses the given Go data into a buffer of limited capacity. +#[no_mangle] +pub extern "C" fn brotli_decompress( + input: BrotliBuffer, + mut output: BrotliBuffer, + dictionary: Dictionary, +) -> BrotliStatus { + match crate::decompress_fixed(input.as_slice(), output.as_uninit(), dictionary) { + Ok(slice) => unsafe { *output.len = slice.len() }, + Err(status) => return status, + } + BrotliStatus::Success +} diff --git a/arbitrator/brotli/src/dicts/mod.rs b/arbitrator/brotli/src/dicts/mod.rs new file mode 100644 index 000000000..40d6c1696 --- /dev/null +++ b/arbitrator/brotli/src/dicts/mod.rs @@ -0,0 +1,97 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ + types::BrotliSharedDictionaryType, BrotliStatus, CustomAllocator, EncoderPreparedDictionary, + HeapItem, +}; +use core::{ffi::c_int, ptr}; +use lazy_static::lazy_static; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +extern "C" { + /// Prepares an LZ77 dictionary for use during compression. + fn BrotliEncoderPrepareDictionary( + dict_type: BrotliSharedDictionaryType, + dict_len: c_int, + dictionary: *const u8, + quality: c_int, + alloc: Option *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut EncoderPreparedDictionary; + + /// Nonzero when valid. + fn BrotliEncoderGetPreparedDictionarySize( + dictionary: *const EncoderPreparedDictionary, + ) -> usize; +} + +/// Forces a type to implement [`Sync`]. +struct ForceSync(T); + +unsafe impl Sync for ForceSync {} + +lazy_static! { + /// Memoizes dictionary preperation. + static ref STYLUS_PROGRAM_DICT: ForceSync<*const EncoderPreparedDictionary> = + ForceSync(unsafe { + let data = Dictionary::StylusProgram.slice().unwrap(); + let dict = BrotliEncoderPrepareDictionary( + BrotliSharedDictionaryType::Raw, + data.len() as c_int, + data.as_ptr(), + 11, + None, + None, + ptr::null_mut(), + ); + assert!(BrotliEncoderGetPreparedDictionarySize(dict) > 0); // check integrity + dict as _ + }); +} + +/// Brotli dictionary selection. +#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum Dictionary { + Empty, + StylusProgram, +} + +impl Dictionary { + /// Gets the raw bytes of the underlying LZ77 dictionary. + pub fn slice(&self) -> Option<&[u8]> { + match self { + Self::StylusProgram => Some(include_bytes!("stylus-program-11.lz")), + _ => None, + } + } + + /// Returns a pointer to a compression-ready instance of the given dictionary. + /// Note: this function fails when the specified level doesn't match. + pub fn ptr( + &self, + level: u32, + ) -> Result, BrotliStatus> { + Ok(match self { + Self::StylusProgram if level == 11 => Some(STYLUS_PROGRAM_DICT.0), + Self::StylusProgram => return Err(BrotliStatus::Failure), + _ => None, + }) + } +} + +impl From for u8 { + fn from(value: Dictionary) -> Self { + value as u32 as u8 + } +} + +impl TryFrom for Dictionary { + type Error = >::Error; + + fn try_from(value: u8) -> Result { + (value as u32).try_into() + } +} diff --git a/arbitrator/brotli/src/dicts/stylus-program-11.lz b/arbitrator/brotli/src/dicts/stylus-program-11.lz new file mode 100644 index 0000000000000000000000000000000000000000..073a29abf767f77b30301f25762981ba01f7f8d2 GIT binary patch literal 112640 zcmce<4V+z7b@zW>?wxyQ?o8$cg5(Xd&n3!42?UiSMyqqC0txRbS|4hk5|TkO6Y??{ zBJ`h^frJnNF)9^Ms$&aHRPr=d`jq<6HYl~RYK?D?@sYNv#hO-qjII4NwkrScZ|!~V zy%VDL-_NIi!rXJtKKo_uwbovH?X}k4XUQXrzPtLxpV|7xY@l(^rlS+n`)|AMzAs;S z_4)7rhgTl?)`Fk@VAl6Pw04;5YkvR7-(2@EzwLHC^P<21rGs^Orjs({^e-s zf8X`lwLhxg^o^BwJaYH%7H5CrpZ@sq#~)vP`9rIFPOn5+*0uA7FK!r||AycEURs&B z_KSb|+M*NvBO_rv=>{=(wJ_vYM||Ha%WQ=Pxx z`QxW2W)DT#`){9^h+JA6Oh=0P$bvM=&*tesSNSxLr{k?Br+2mTU9NYnr>5T3%XjCv zbU}Ksp3m2HqMpCdo@@Dw?75botLxz;AE4CXq%5_7r=zW>7xOgHs{bWC9c(?Fr+2N^ z7V2H=>3plZo?oEro_fB>o@@Dqx*m=5my-KvT($Dm3ootZ$Yw{r^Btb zm+-Ww)!I_MYdyWf+NkHtbe*i{uhexz{wgvZkIE*0Nr@A2{%TSuT1~!2se9slP}R3m z3!WxhPeXcYHTKIq9c?|mR^^Y^^NV$j@=JK>DSD|AvV1uSSqZ`lrMdhv(p;H#xzZNp zSCF=-OuN#hN*!!oMe5PA2d?VQymtQ0a@aWLJ|JdGTO%Yor~+!?CJO}}>k zR@am52_xr3iIn_wmAf@Rtv3+>$D$E;`cUM~V3ge(@*W}0-gwHm)3>O|P0iEYXaH(htJ(6PHRLKV}7;vHLuCc=Li z&r5diE#mRwi6@amBQJ}iW;WE=1rKH0csXib85QXW1qEtU0*b`eQ2AqUE8e8ac6GH&onGeb39GkvNg{duFF+N^I24LjK=!_=*%e% zSbC_QinFp#sCO)d@7;7N*U#(b_y&Z&Xj$|m1If)=oIIfc6)jF4ueko9*X+n=g6C?H zjyGqItmI?XDG_gN(U4B z2V=z>`yw~j^<2)S$5ml7DBbLKU03&2UF>SKHqt!1h!}cZE1L7>xG#36~6zL>xo7U$na|K@I+F5q#_pX!YsEa(=$FS-z!%JMt#O2YUN`4rcqEcADE1libNy);W7cs@J$C75{;yxp!UNU_oKt&G%x+<;$CA~O2 zl1Q%E&}Fo56{`UBwfiN}z?`RRInC2kN#Hz_G%G5X&uPX*GR|O$3kj@bg`TP+x=Ug4 zMBSb#>6R39M8hy8$KqrzchNvX+6=v`kBB9?nK6?6g>@nsNa|vCuD!DxQA958^3+8x zUYwxBxpZ-&5^iL^T8x8@Q>RK~hXh+P=>NH7K{|6U!&^5luNKk5=)3Q} z1L;)MLE~qXSFD*p?W%_bjmbAXkbO#`hMajT7%f>78)GhGEvIST)es%EX z{WL%|DGd8CSjiESJTFcjB)!-jkDz=pmlyZP9bCIR_FQ{!3`%P%f{cK@)!8mEi!u-c zVjk0qG4;LiY{}nPNYglC7U`l%TWD0t#5f0yd6&k?ccYP^cnNHh80$c5fuhPq5eCL; zE>GSLz85Fg^Fjm)-7qj0CpXc}q&RP+I5a^nZiUs)i7Yg4%1X>79cS7=cgnj!KOkL# zEJ<9K(SEl&FUAe6tJy0^;_@mkq4faIYYhlc<)J9jWziNg)fXqXx@5>LNj7kyu89N5 zlH?}dF#fJLB;MoUzW=M+OqW z*8ocvBLMK=vLKd&7@nTCqdBK;2b$=2f~^W_`-CQuT-iHF!sVgkMbhxQbLrv`%KC z{eBF!gy2`>DUvCO1`x`um~7T~(myZ}>yNPzF(QE2*e)#);7CH?M>ZeRuw9(cQt{yX zxGi2%eC7RIvyTWi#2{55-WHpLvy8Eqp0UKBsU%V3w~|EiDqwL@3yZ)ILxMu2zg5GQ zCrS6Dn6K%(i3%q2c-}yc&G&Df79hyO{Q_Y&X)~4ERj8`TxTJvSIlsw(m`R^_WvM7i zkQj&;Bv8%8rF0afY;D*~Eq7BeshZfDm+<<*AJHN$X^hpA!J@aQWCs8`YHFIP6usGG z3mwp@E>`jlmmF3+E3CDr71~@>GTMjZPV}^y>}R8A#wpP=gGzbxJoH%SPDalRp{ITd z^hBoswZ&O)hOs&j0;Vd=XF^z>f|vS9c&T^dMV)6j*nlg3I29d@84(YQNi6w#v-cRbi#Cex9#?Ge0A`BUx zO@#VdEEhHRj;PEdDl?GG2w7XrX!%%cs1;c3)5Y0u*#Kdb zz7QQoBUKgNZ*V8YJ%R>(BeX@XO2p4iWPP5vO9mZ`uJSg#ijp7tB_(Ez3`JgcBK&(v zIN}keN%3fjI~!v6{ubTK9`!@zoT#i-_?Ef_z9ZGh&@gk08#Ndwc~}>!~QlSm0|FeP5{&)7Hg`nr&!jL3$!yk{)45t0dxDS`xXl$BeVWJbNh4K4EM)gev3}nF`9YrZ4q(GAX6t z{gt@4flqZ>zHIlj1 zTY50bK;Lb$u-Q{4YhtFl0m>aR<#a%5LwzLM8@gwzFR#=_Bb17$*%wd~y{%MHL)ECE zzA{x)`f|a|GI&V}(R?q;p6LnRLe-;B)u4Atb0{J2cK>l;QJq{HNSW8#z#Zo4WT2Ka zU@*)Ty|0W-#}Jq`51(Oq5r8Fa?8J0$ppBXb(TdxxVWaJ6O&asbtxYh>WxNdy$O0Uc zu)pj|D%+D%b=cEh=+Gc~FhcGjks}$@KP?H?vKFifS>S8_Di+`^_q5S?h`k;~j|Nyjq^!63Z{uETwWFxh9u2z=PtN)slz|K&|tbf{?wzu%L#esu;3SCba??NX`=- z7bJ`F8jG|vd#~{W17is?7);yjsve-wEK0L8xL94@o{`8)62{Y?#c_(1 zCBNAw=p;kv{p!bT3ZbBZJY-5=-r)are}9 zPH0RN24rdGu^E8iknNYr-Voc11n6ykq_^1)b--ZoS!H5lWuDzprboH#^OO}KL29X} zP(^8@>2J{Szv$3a4>EnVdM>pF7mvzfi$v?^hi=`a1_G$qgBICJ;_%(f@_Ag2a?)Qe zGz}!Lk|G%<^?x>mAPN?#gBUdN~$5sxzRH7L6O5+Pd6H#y*gqWPdAQ4MLCstBG1Da$Quz-7_+?*uYZ1 z#X^)Joh9Z<7^VW^L6Tf0dLnw&Z1W9K3&!bwFX>w#@ z0<#Qu+eF43R=xSX@Q3q(>)7D@nZDKSXR-W@Q9Nb zM(W>KJn~2&_p%7D9%+@$Cne)pCb@ocGD{PZdrwZLQIfNhlY1vUjyyLOA7RhG$ zz~Z7I#uC)bM$M#%E{Gq)E|1ZAnuY{W3t5hS|7O}w%k$CvkdGr z+LlEqd1TRLO2q+Q{#gctMjr1)Ajud1_2kr!Y2Lv{8KTb+HT1s|oV$)H2n7ff+J zKUM}SH>D$ZPNI#Z3l3yJrP#h{MDh(t3}D(L@oQDYl3bD0AwzY-kY_TwgdSq$FP@PB zP4dK6iI+oP6_P6ww9wTYW|cRU<$p|F#j~Aoe<%q`NZ}A>1vY{r&SQfI3s1B{z!Vb` z6=X+9oS3MS3cOKq_=!y;#oMhd;_mT9$nVm_0 z{Y{dlb*3OLK5)E;J_ssWLsT67JkLzw_I%(m`dyq??B1{I({b^+|Dl^trr9?|6M7Qm zG37q}u3MNa%AP#AeWWj#tKCyy)P_Yz-Er-XtO?$%x!iT6`9D$=6Ie_eCknEd{DoLEkskZHm696 z)Yay=I+`{GnO~>x)U*}R&$1xY#&A85%sl1WVvkk({2YuAtlcUDKBzJ8LJ9()HQfo< zpIHUuem)Fpc5dD+uE3HfTOZ*z#miAaR7Nv!l{K@po?h;%#T<0p89oioX};buL6Fd` zWf^$~l58+-V-<%2 zI(T?_8)0(qK#FBi)iFaYi|~%MwIUZarbEFCBXXeCd`k^C9*U)wnD9VSGM=heiWdQ` z3JU-*)_sPgL@_Vp!AM>ln6rS0hwl@O5!2(Ni~WNvjA*N$(+h4ZjBg})<5-eRb%Ipp zOJj&M*vfxmt!P>$j;e{67T=z_l1N0OkD;9LBTKQ@`_QK)70i<3-%HaXi-=|Ewp?J{ z7nbd!5T22+z%3$dPrCq8D;`m?HctL$Kk5Lh@9K zay7v?tDu-;f-o3R*Lz8L_)cn-=2j^l@wXYbK$m8JXAIjYo>e-2k!J)^_HCbfs=A)} zg>_xpUTtZ;qQ0Ov?WeMMWdF9kBO)a;-sr|A^LaPY(*!YiLl!Hq*Crc{dXyQ38t<4Q z7mgwqVo$qbrctA9S2LSfFgo_OWmBH&<&&}u`Yd_M9djtvGRd;cZ*~nW-Q~K5CVrak7#i5M zQ@0a8uI(5i&`Y-yC;E2`)pzaGi~8=6z&#OCmQU#fNvSw@OLy?v+_{iLpq z#&slD?D~JZx zm37OV8c@ z`RzPoiW>j|%l{9U6T|HfxDB~}#BHeHb_#^LnF(%ky~G__Uot`$=V+k4xjD;%nP$7@ zRyW7Z+Dh^Z70fj2P0f_ROP-XT&QbbxZkC&^`&q83`zB$FWD<^e7=rVOS0bO?HD@bc zX%^43x8`$1&(T~;omQF5%Mt&OMc~7ceSY>(Oin<;jsT9g(0*mmqW%DKz|Fsw7UtQH z0`%OL+-ZGA6y*hZqAnGSq^|mbgJLYA`EtJ*>I&e z+AWdLjETQH3n=Egd4zsdJYO33d2H2N*qYbE*8G62*swKU>9Mdi-(!n=AlLmoutg@1 zt$DN^8@A@#-AUMjJx9@`J(Aq4CF#B%Wb&LP>7=baD$dz0(<4(6!EG<1lBPO+HvcNc z@$~sfJeIl_34yu&2#d`vc~cdz_&KIY2|a;~^))(w%E@k1wP&(Z;w#xH5c8BS=jHKqHwU$#g7=rGv>f8?=>$(XpDQrGeyV z4a{?Ww$|)pS;B~pUEkG}L28SEt)|6n#r45DfL9a9MlaVl(mY*L*EB`9G^f*ns+)Fs z^RzknOqx1dPggd(FQ%fI&^$l8;Z6q@phREq;vR_6-E?m?0)!8;RD5sl+zdsnpz6rG z0V8LM9-F1rO{JLZVdM%5998{71{S0D$MWjO@DYK^4|By_K$Q?JDp*&Ky6WW$`V}oYW=a?rj36^Bk5iEI@%eT6Dh=S%knn%9Oev<|R zNiR}5qhzjo(RE0dvvg1H>$n3tWopj82E?7~!z8P2#tNj^jLXORx|IbOG>#~3cN(Bu z^8ub`ZOx_3p0PMN0Y~fl!@$|c^-RFfcXcJ)L>wQm)+ekP?<)l=FHVlt!4EPjKO3Sk z+8{3&#&?H+u((L(&6K1gdJ*CxXA3L*gK_AK$dw=k&fY@jg-Q#FM8+VGkT-84`q8L} zPGXXDo>I>CYybLF_^zodOo9C zURlOL8WdirjHCnrefm}AkD^kxA{&q zyVCUtU*jHD>;(=yaJWY(C)c>DpHVy^h1Z85lg3sF zX;I+ICTKl(D310Xh)TuLTucuF51vL)|K@_7X5<)^AB%Bg-Hy!P5F-wh)Wh~Jvr6==1+FGI~gYAFMYprUi22Zv?^3?Nb z;7SS+s)o?X;>hmkdsP#(F6!+b!H2T1g)z#HC{UH`vs{_Q8DIu(DR@2;&DH!oYE@f7 zn$ye>s8@B8w@cDn%@$0Eo=xQ#BC*+89lDhv*<$y)$=gK7(xve_8GL%!iPFd7lDx)g z)ZGaKY0ra!v{S=yBJ|Z)h6BV@Se_2Ugx24{%?bo)_V_O89Zi(zwP*k-iC{8pvkLFv zas$6Nyc6n>k#V!nC*bLH;T?LuR7O>Cj9cH5Bi~`nd~)M2C-z6wY4=sw;<=?ucl+lK zJk!{K4K4!}Ivaf1eWI)8Sh+~mOIv0%70|`%BM`Q$-H>IY!(HvRED4k<9@U5`F7m+= z0t=*Bam8a2GdVmgn_ zSVnS>aD){)trqG>zwbAN2ScMU9?_DNbP!QR7oDNb8s|d&(G~!%5p%5$8!V=q(U! zZ$w-BNTq(^Fy*n4gQ*IF{;$|twv zgeBXkr?&PehmF`9#lVmpV2N;zS5^n;=N5vf+w)976Y&${a3{u)jYWNYj9WZBdg5;8 z=CLuH8lTf!AIoSTCHP0zyYvSTAQ=`w38%KTDYQ2ImzaYY;cnp{{ zjHII5?Skm^CDFfz)c$7A;^?34VPRBjHkL)-(L)Vyo>lgb_Nr)@l#Ra6lc{{ZCl;Ya z=jzFz80fQMKScuqznxFuFQC>Q6%(^?L_L^yXi{2F#1Q9 zs?=0Gphs~sDAm3T4~O3DwKxAN%en@o4>O8!yl6Vh+Ng*x00pm%HNbI$qo>99>$bW8 zCPJV$drXAOHlH^TR0VYl0Lkb%c|(v(h&Fqbty@E(SMve8$y|4s4HA=4>o!P)wr-EM9@jGkZ6HXKDww7`IPtht zO?6!UvhN4vG=#=@41N^~RGG&vRFiAemk9HxfVY{`^f1u8)uu@!Mlu%KvMCm*K~m#W zaXUDw9Yg{l@Q1Jl3EMN6>c}qlBuU7X3L_BC&1GHS+a`Rg21}QLf(9*N4P_K@O`VEZNo#ZCgg%TL!eI|q2yWY!b z%=+34JnzT6B2ZY*m~NGbDVU97(CF!?MoqWsD#wyc?BoLfzBwL1n6#|CX1Wz+d2jN> zYC+>%Xrq>{YoxcdDcfmAwH9}QC=|MJYImUyfoN=0Vk`Al@QUX(sdz;y^t5~|MPlVK&8+MH+B(dBOl<+gNB)PB`i_hll(m_8jrd%w(#uCoDCn5n5l@#N6*v z?NXJIq-37P`4mz!sFZ%d`&D=`ki8dmOc97`eW{O!Uc{7?21XNA;Z`bdYI~o?o48Gb zL$y^PiBL+d%c59qunQ2$-q>uxjwPBx9GS8)9`sXhvJ9_ee_)0Z4J8c@6a+NF5+vI3 zVV!!zez-7jgafoJt-!NCp?HY`RZ0-6a#=GKd(f2AD@{m>JNQt4VoaQ34=}yzZ8iiTU|IUKnnfe7Y7dBSBF1|`{eK?| z84-&qF-(+U>ywh^g(?vm?c1+l8kJ>-mMS91Ojj9V<0pr)T3)*WRafL*ww`15Eqd$mQa;yq!OmQKxriq?YCso{$& zp~I1<0p4U>&{~-%nCt4`XOQVzAf&Ju!QfbP%7;ZLsO3X2Lk*Be=8&GUBI&CgTLM_5 zx3>g1y!ZIumQw~2Hbr2Ffvr||10$IZ&4?QRq)mkE1R$ix!ds+#!!*E4Y0OSsltf8- zu&C@oRdC1D5&)?Rba|7@XO)|YmGK%gk%9gyg|n>*2Bmi+dq(qyixJY z1Ey>A`VEbmD`rS)h_nOA(qRYb2Z@^B{(Hdz<*Urn22?mA8dITTinP;K{nNo6&LJ>+_l@fTbIF%%JM)RS_ot1q+SuN}pA-%7H6ASmt z9J5K{To$L{nl^{uC;t7aT9@CT=((z+BZ_(Y{O=LyMZG3mik3Mzcw#DKra~ zA6?`qL2^BQy>DPW@$N@E3q(3%#=$J)l8>Zmwq7h+X$3%`VVpy@%HH&3E29 z?fW(qo3n8CwOSx)1vS>l8$xs!YehQW3%23JZU!+=o{3BL$msSkXpY1O$^q!4XbGh5Y#eKpbeKfnaf8CE7&bh0@uaGparCg zNdzc0IFm#5(Plp(zDor%TlX79HzPB_iGw=O&kT}l!QMZb_b|O^uy&f#;ARLUvsjPt z-=mpX2Nr}uXriFiN;j=!qoEZfO7+UhAY&*kQtvt!MEwXcKrxp@{vt;0pb9ke+nVQI z)6Aqj;pl}$U*vN6mOm(DO&a6nxwM-Hb;5;BfDEd0x8iSpk;v%JmC8>#S1K>hl{#{J zVy8;Y)~Ql?I8aKVBeS=)PL-PNX5lTKZKq1jwo|2M*{MTz|>)=4C zO0mbFv(uzzlM%Nn6*G==<(y47D#e6l!Rg#|`8G1VXiF2<__cj;FENqE*dmyyi~k-c z1~OB4o0fD(3LWMMxgHK_BI70CXYMulnWZ_VN4ptEb0;J`jw*>BxVhW#7-(nqQyk&Lj3DKld#quG{vvMpDvvi{F4Ditysu8;xj<9kzyd1PAqZT*((5Ze+-o&e zv>hQXzHgBN$5>BbW{QBIIA$q-%U&_BlHm)ol&`l_m<*yX;&M467s=m9z^R^A4M!}6 zdTbo3&cl{MJw8R^0cGehAmQb63_ZGBd@xw-ly-tNa8*3mXjkIF*wc|BP@#(n2H%yW z1VZ!GZahXux~-Rp0X5MHC1S&x_&IP=LVamBp}vMDs8($p!J2wVkUmuv8tACtF+_gG zQmCMna@bO+qy;6m&nK9+Rnwu$lIV{#g+Eap$zCB^7j3%Rc8)+yB{w)V>5S}5w(G+c z$bJ|hyV#+QWih`AZ-AvwE131Im0&tQvgz9N2b>_9G7xB2nG=KMR3d4hB{{4eXTPH5 zw<(rVE42!IquEdzvTsllD*3fHOK{q?6(f7s8jnh|PD{<2HhG6mua!xlREh%4b4!A! zD9}8&1UPK3gIQ8(d;l~g%fvzMIr33~3f$QaIITp`IkgRLrj}y#$kZc-wp0l6Ql;O| zUd=AENnvjQJOB~`F}VTGnLPk5x4XF4`e1#I0HI zAII~(X+QSlx*|Eqfl|%r<1&L0ty%P**we+yFU1n?PW4Q4`Ga>RggR)AIh{2u>E@ag zr8t=6fMwG>C@_nC9goaglO2y#(}EgL=*}d2WrdwzRWpZRw~EaytF;R}g8HVF9<^p? zHX3980Fk!!01W6r4zGB^e8z7;P#w?S+=K(s29Yr023SShK-2ZSn$5~#M&KL@wjZ+e zk^ptxH9UPTZub_^R`-HsO7B#i_(ecO|M7z-4(~iT;c-<=zSC~Q z!4pUHBKu9%hJ_-wQElXbj&>SZY~vhmQ%E;&G^pvD8SDtH|JZ|+mlreffv7Ac5XfW~ zTPf9I9&CZ3!%v={T_$_;%u&Fv{}?Ee14ykHuTc@_qWsi%YQwN2A8?tUj6%0hSFs>i zJWm2qI8ftPrr5e;k4m7xj@GL^rp4p;(x>7j#nW#WZhqI}#sLLFO#H^3AfSj(;rO<|W}r2B=2;D|+OR@qG!EK;Wt1ijpCRjP@gxS~McdX>qPg zM{^dUXdW+NEIthdv|R}0Ry%?$w&$$zV%{yD(Kn*^2CU`2d2-VwxYG*=qz%$g9NmRa zt0((oksF;kWtE9B1)i9Ghj9RUmTdr{e1%mh@)$bc{tzVp`Pztau~-Fa&CFJ|NYe}o zjmxKnnlZb$Rs(VZdvN_yUSWuAl0s@34L_j2Tw8P@{vq zC_6~j*iEy>Tpv&{0L`F{oW)$3_lkIsPp~!9X_X|3BSloOzKi^!bf980?860v<>tIs zvWgW)3&WUy0@m|>5iQedWeTYM2*}zLP&NL&Qj+80FnY`9-w*!V)Y7x4MbTDgBtIB3 z0L)fUxn+A@?+U7tbn;N0qA|moLtT=jN)ObxCZ}ZHFqS8z_hnAZBbP&5_Xgi1c z?LhiMvDT#EsMhS?i1Nl8lLX7WuK^57zel}7N>Pi~ZD9w+!7?*tXe>3RaSSWU?jJf9ZQC2RM}-M58RgZF*vDF1L50x3#{)Fp(z&e-H(DI{ zFdlmJ)4M-(=;6ITy4>q+s2w`MQ$8HdT-ho5@`!#@-U}`*R!UEoOB!cJ;1+7s1LxhT zQnMS4#Y~H;3v8#^gc6$-g%L;{CJlwmAF2W3IFwABFa4~saf&A^4Ui^#S!Pl+jBn|_ zD&UaOtYIQ<+Xq?H`@AKyi5i4@hDvM0h9Tn*!C;ixk^lynpkU)%&`M{ z*sA?e)b9K?86`E&gPS3KhdZU2pmS@^E@=P0m>rRK4t-ow*KIQ?h%i(@e&e26T7%!}9iGypR6_&? zU2mrFjBTSM0c2~^!TzR1!az1Nzt`6CSVuu+F5JwlY}|{{URiVsj1CpWv!tmOM?L@H z_GVJxAssJj>wxmD+I#6$a5D0uvVs#FkXy#9&F&$$n^g`~b?==L8Y5hA4mm7}$D-_merN!j@I<$HiThm)s&J&*Ml9nb z7_iO0+(TIlq+n(j?)x})hcy<+ab~(>eHvxIqh0hx@cES_R!0MgyV%^Dptml66@BjM!zxmVWwr$zbISQAg*Qkgo{aOfR-wLP{ z4Q)!vzK=uO!j#$N~;_>V@9+;A9z=;_`szqop+yHAKf-Bx=BZ2wS-d`g&b&NYN&#e=2 zfZdoG&yox90u4J4{j|a`DUWi~)PWPVw2RGbdq=J5UPgxIpxP;VE2ci);>|C{WpoZW zQ3z2>Y2;SNw=gllF{JuALz|Ug7$>JAd?Y>62A;zIHgWKfri~_|yFbd#_HVS-RMnP96 zm?gh2r!MegnY@hE)agGF&>3MM%V-O$T2Zw+477%9)g*_#>hppYoy1p^Yuv)u2C;W2 z`{xN0;|E#%03W)qxGpKaf+$J6*TWmBp0R|bl8cr@0rHt@ zTOj*%2Dur65@fbWe=MmE)HXbLz0EIcCG=48u$ z2*OO5YU)X^QO&5gHV^#@OvCcCf?iYeng9_iZ}5|)(fu3j*R~1Mn-2(HW>nW&lb8+x zrM9_k+@mNO(AHS=l)=yH$%3d3L&Js9KYaKOR2m>Yq8sTMiPkbFK4eeV7u|*RGVn@q zWRi5R$7}r~TwKpKJLT4bhP*@bmn6?xLu?JSvY_H~50Rlbtl#WCq8aht2tpNCUPZ4r zk=*vB07D+#aQ-lV$+K2ygK=WXl*b|wJq3u8FQ8-|OAawvFoRp`K#oPJN{S>cI=C)c zvB2o)i;9@^;o}+$FujOp+}~nSl8r}|+`*$>Soz!?0}N7M?<3SacfqB5J(sGVp1E#n z^VVw5TG#8M48~b&1w@NC&3gF@7>fep!QWL&!q`N)m(JFYOinF@$!x=9n~0J@C_tm; z@6jYN2aa zKTr@IsglRE%uS?5I|CD?jD)in(fTDVdd{Ao|SONWWtsQAeVU6zm+ zk#UM6TxrlyU<9)Ie2gj?e9N@p4&N1RDT;* zT8?;*Q61h?Awg&Xb@Poge2y9Zly;q+q=C3cV+alJUAk%r-m9Bn)WR-+qF`CHvY=k{ zu4o|okn&jVN~76mLG+N)G%Q%5E{LqUC8@JgQyTV1!34|%m?4Qi^yI%o!iHhjT%ur{z^BCF)s5kMC1++giMmKl%o0`!a7wuQV^escL z+079OfTUq{QUkW+3aWw4$kck;(5rU^!T{sWCd6SQJ$ur5cea|+#cJlxy{VbZ;gB39 zoE@5ienVP|+kepxXs_=wX$h$fsZk)n0ruK3LJc#%lOmAcn*$JkI}_PA}4!n;yhq$Jt^^1}v~WLgntKlKl+9JQR4)rDom!Y~o?exJc@C7Eo> zDN@T3e0a8oXM|M!R%;SVdvPP9W4K!pGd@@2!znhape8G1F#u(FTCk~(1K6y=p^p(o0uZZ zKxko@l5a^dZ=>86=7SFn88So$Fc%tWC0WJRKuEH$HTwCnF&?<5h$Jr4L8_Rr*hS(u z8gLLsFfbQI3|zyj-5FBtt}a}22R*}hRz;wX!W7Vq^WLYRjUUiT)I_x)x^HSC(2=;S zoygNq4_dh+V)`cueba*P5TSCd^U31kaguvNvd0?pRUE#`h|SQZLOVNQ5|e>ghDed* zG46{!y&7AS7`_yJ9S{>2yIaW$!EPl_knA_wQ@=RrgrYd7R4?$3c7i9sl+XrD030m= zI6#%{cK7krZf^4A<{nfu_!Fce{7x$m_BR1C5feLT8+&;NKo2r9ytqmBq%CqvA|mtn z^+e6?HBr!}8gD*(T6pgl49yVJG>P}r8W!ztmpevzh>y2r7F)} z7ENkRdjM`VsIt%c>XG&hZ5Wza#Nx<-F+_m^B`gkxvD(kE@$uw6T^EhZ`iU#(M&57T-0beAq7ujXXcJ zl#kVH;iwdvh(5%T<*iZPsByyza-~# zWqJl@;hq&hm~S49JHN5i4hL~&I)PWqCs9lK)QMj-i8mJEoWq>-e)|P?KzjCW$ST=3Pl! zgONg*c<_w{wNmk#VB%FcI^NPCEXhWBOM$Q?qfKjOH;oWV3oN}BS1d1ua;QG`+9GqS z%td6Try`|r5yq!!076{rnX201f?D_=Y^jS<@XcR$p5|g6J49CwWDE!`=}l{*aZu`qt|_FdLLDuWz^5gFi;%=(J#waGDcP6mN3w~fDjQYr&T+#SR=XkS5+X<(*xmLxKiM=)6y0Ofd0 z8_E6|nJhI5Ib?b$#tt#%TGUb0fz>=g*R)_4pq~$EKV2AQwmX6%3!~{~#xfVgS`055 zm21gl&H>FqsDwHn%^Kt3){Rn^B(#Gynl!wiOE>P{r6cr&Wy4>qLA60hye>FYwaYxH zoP&l?AwYW}`~$F-pYpLqV>uVt2gCh1F+Ov}k=w;T_uUR!37>F<<9O8Km?heAS|l37 z5!rOYQs3pXZfx6f2nPtJoT4Tdh2Q6_0}q&+O_~*)USN@o`cZg5k{uNx+2{gmF;RMf zWVa@d)HUP3jvcX2rhMLp&|3;JsZMknkQ@;hGpGrIvGY>aDZIEcbjgLR{ZHoReWwZr z-l;5%-U3l7uq-Pq%S0es;L{3A6wLWPb$UB>k!CEPI;)jR{B-nd7PKoPN!i@Yk?a{Y zrw+eGKdallMrM)?8yx~a?+*KR?H~i_EBI3WB--muOu0JQfP3QWHku+0pZWs?glX8s zSsPgh2_xFms|T(=sv`ut6&$A%3(%{ooFu>wn1s-OzbjwWGPq-={7M5gMQ=c%P6oIY zU!NC2rjMz0UU;ygW<$VeUlf`^-D&IpoTe#Sb~*z7^Np_CwvC4a8FGvnY__po-O$DQ z7S0l|2Lfb#nWn|9uRg^x*`C&)jqHjiwMZB8_5N_h_^$1$(|6K zDzj4k;X8Id1P|I;j@;D|)|B({4*=B&XA-DcG6k5!nFQ>XV515lY8p0HQihSYYhh0# zGNR&N0Vaj*0*#b;Aw`coKxspYthMAa)Tj)x8@_Y}1W?)z2rv<1M^var0jv5eP$v{| zR0+ELY#MgLH|Wwj`b%|SkI(iZKM*Gq6NFyK0Mq@0f@ zVveGiXfEro%1=z-x%B8Flk97y-Iw_}YE&77l}3e1)OZCh)kD-c7&9+B?hJBql0`8S z>={^yy2F|1V8=j)z2pEX5oi_<&-oQkNLMMz7<+UEGC~c;mb&Ay7lV7cTjHL~o+^vu(qyXg9A!r%D>e-66V$qgu5-37TF!pKJgh)a~VXm*M35nkhnYAwN z+D39}7PkH!h?=;(?b?PgNeO6S8sJ_V3S&KWZ`;B|<@gM}9qy$8*1e6FUDxg>l}(v$ z_s%V>FlyNN1k*Nkc-s~xje6tqDS%hKpjX8L9AW%P4GNnkm=@)L?f~6BGN+BWzEtB^ z$^bUaSQ0`OxhcMQ&Bp!B$}TvZv{5~=Z3>2Wm0e|go8{S+!%d1#0dIler+xEKT zw!Nd67WfuJ_LLeKQhX3_?#i{J*z`b&LwyOkN0p|7$sl!hYZrBo4)1IXMQ@B6BOW%a_F^A&A-7?Ygn2?!XZJL@-X z*b8fk%(Mgq&3+NaG*D?9R*mP0=Ct-3%5>zT7q$na;w%fB{**KJi3ZyBYEKB|GRcId zAXp;zbWy}u4{}bkXJ-sevu@ftArvtsQ>WMp7z~*UTk%{yWf^ThlmI2f%tGN5J4a57 zm}{o~!{zefoFZzDOh^U^HHHvYp1tChX17${ngAt&5&>kPD>_hzYEh1bBg=xO;mW8L zc5#_U(x}^7W0<M%mxmi9a#!30is4ZEiY(OUAX`%EiNq6{8GZwUQwp` zb}jAes5_pb9f@CTC3cklOe@ig0TmZmgG-{qVNs zic{hwn{|Ddn$N`OXdbJG2Wjno&oRQG(xI()c&AOhgL5k=CXM32J?Pgc&(E0hY-Tl- z4+fnd!Vwc?hg=9e5f+g`rKcC&GCjWRP{;?J%dgcjk9GOu8KXwwT-Ru3>h90{OslMa zI(_zpUs(2XyHDS;@Zkioq(0t?2F-UFVSDd1h#Ft$=2U!1>tvf9FkvEx(395cyL6w( zD@*wBRWlR}`6I{F!RKfJ$l7yDPsB9GpZ7x@^ zg3oUfMOq+Jo(M@5|KaEYfn7r3MRp`8mX@idxQ-C0z@P2bY%`ll;i>!X`<<`eyZZ#E zWuSf!ed$9_ec;&szxlD^ab0uh0}p)X9Y?>m;~V#JQcUE|8T!pXy#L{!e09$gM+{r@ zhraaL|M}CuJ#^@++OETx9Qwi0KmYilC;#d8E0?54mpg3=DHSGIVV6|GxL9Z+}wHhxPo(0|e^LKyr=r{lL9s52c9PLxWryqOZ+h6(W`~Lm+foD<)-}(Ia z?)vwqcK?W3=RrMx{^5^)@6n$;`-8c-Q}z6RzkK|?AN~4w_WuqAb}QkXfA-Ao&p-6e zA3i|Bgc9Df>m&O<`cGeb{1U9Wzs&Qu_I>-okACFQoqXcp_%FNJVEABM(pX<)3w;$D z{1;9FNraj&(ErJ#)&Jr+^4k-vI9NG3`S`q%?3gVVDd{u5rk%166K5-Zaj@7WgR%r1 zn`h@#uyl8tBgi?W_QQzS`8Gxo4^+XJ1nRFsp{_Q-3p)}+yoUkN1f(WqV>|y(j5!10MFxvAV4$a!})zp zoDkF7>smx{?H?CL*nwADL#mF7|GbOMs{TFO0@9)kE@BhLuc`tq zxK)o<;R~bhz7w3yc67yi|80H3>sEr_Ga8mH1(*-R+h*vn!0ucTrV80r)GwH=YRxH= z6_8e0Y_sB+xzRjQY_K!y|rwlz-I1hkGvxct>9(v$s zMM4ia3kC$FG~J9iEeogtS4E*6ywQp^V8e$U8$)AaTBiP*5J8!g@?5$>Fds<6f<|2z zTidvG_&`?d>%7{|2O~0?`4~)y!r*|{9<5!fjnHTkQ1C!23KFr=R=4h0whFD)vNGA+ ztg@<$*svtrV$a@O;ThRPMQ-C8zgHg3&i7d9l*S5EG7_d(^?gc@ML0q{T8uajf? zgj5VeeJw;)cy5L17>|qwdI$Gg=u4k@_mwklj(R@*ir*yXWy`X{o3qYPE4cXxQ95H+ml8*IMg2% z2e=!kMNw1a(YX1Rwc~3xti8>x z-#k2an($=e%7^E zy?R-z_#1AyVVS#S-D=;`%GIl>e$$QX$8K1&diC(Sn^&$|`PShvw|?E)+upizliM)1 zX4SA;v&oIGU+->O^VXYgUU{p#aTwTEZX$KVdeFJa4d1$IczE^jYQ22p8(v$ul{c@y zW!-oyUA^dhvFU9qH@J1{$*^wa4Qq#2kFOscUgy@Vb1T=arX@YBzh&I592<7yHx0W@ zD{mg&0Lp(oRQoD-!!0-7I6O8rjeZP|Z603TnOXvNsx-Q=cICQN!>iV>UAuC8cx>g` z(DbI8*59&rwY$ML?7+er&_%b`kGZw$*S(cqZ(0KZ);+Hz7*Vg@cGH^i;k9cvjSsI5 zSuT?#=t3|PW`K#oHj^P4>I@xh4d+Gn5nbhBkVvd5Vy;;W)(AUVUTO-~(`K0&>kxty zR>yKjTCA_|j#y&SN=!nc=Cy=Rj*^wBp+=+K3?JJ|#y1W%*&Nh&QVG}Gn6q;Xb8p_4 z3?^(XD}Kyw#%X324`-W9lS6>LDG^YnHi>X(g2ayLls zS&OyW@sXyQ>t$k>t;c3|v;a&Jsk7ec+nhSF()Xm&?g{k4fT2^Y$Wb|Ue?&x%j}DpY z&3dS&-mXSO&=-hlJTcVP)VM70dEL;?VI20IQ`<(SqsqWGkqy-B55&dCgAL48O1=RR zep9D-fQQ8P1BQpR^&ka8x&b-^=5cvDz#2{$1Zt@ebX01&VMg&J%!n2Rl;{I!wB%== z_PQ*ZY*0{H5kP4neXeMJ3Y@FQ@VF(nMyu3+Y8qpdN6xl8XO{Zzc?vN6-1!W-1S z*4BEw2|3A7p?FBjXn`^QWJ)wXSBV>VOsG!NBvpeyE*vrgCu!^w$Hw^*___>J-P&&! zELEs8(Y(p-t5H81E&wAL+ckkoT7}l2sM<3ymEP+Ey|uv+XqA;QN#0ak3s%aq9M|%^ zPR8)EI%%I|ngFR3;eSRPBeYR>mj?^hVz)Hhb-VfD&g_6EunkOf(?pk% zhOfwI6NFX6Af-R*76lZgt>8h$Xv?U>Zb8Jdd<1TOYcu0gw>Z>&xkUz$S$VCY2~OfV zVPA?{PF#AvyukqzC3IJ}pyNw|Cqs9LA>!?yEBxg$f3dIZOaWcyPe4CJti1=Fs#;y) zUK{2*0l&iahsG{-SBJYEHxTX&>O;7ECJ4#%O)mDA08YTZyTmP5D^syQ+Pv0XGAc$# ziU?CkmP_0v#l*HT7AIPKK`11^#cNq+9DC1V;Nq7res&cPes={={9ZPv`6h3A@WY_} zD}{Ojf$Elk`@|w`mdHAJF`|~sue8#buvU=9<;|@$*z_{excs+}#(ArR$76=PObLcG zCEzz%F(m;*@5N<;TVi*+ZP5(>Y2iw*D5sU5uK4TB7UGBA$*+t@k(NktkB@y8q2IiY zzPG#()k3L2|Er|7EXh6<_}@Zm@BoU-e8k8;mK(((UB4JV%TH{MPXng;UQEh+)>wA)bv1U$L+NmHXx zxK6G>2~#Ri!YL{^D39Y&URWLBgK8K0ad068%DXZK-bUpU;s8G&e9hJ?>D*o#(l10P_-L*YE#g7%SMRjO0q@nsx~aX(F_ z+6xYJTHn+zwcIRz>Y8mQv~db0N&RIAt5?!zWr9#h!5wk4e=k1QQJ$)L#q`&Cw3!}VbWa7CH#kM|8QPc(F2k08H@FL1uHE1&EWNso3oy*kh;b1FI{6Hpi5SFm z9-uR>{G68qi?C$W$u<>zOep#xdtDQD>X5^*V3kw1P8JTjnUW&)3=KHkPalIx9Drbp_H7|>WbyR9*g zlz~M5>sryZl(+auIw~I|T!<-QVuDTT_lvkO#_%Sgk9+jMb+;ug zPVUw9D7%CtVFo3w*h53$ccEp;1QF)CZfV+46_5_o;2a~T43TRZ_>|N%z+Fs zGwTf`w{nJXc_;;hK#0oJ$qxAcx=+tTYT2(1f|BL5&ULW_8viq7CRlp5^cuqHnDB^} z(E>^XN^kMxMcI!uqsmID9x^WN6((G|V8T(pf4&$X@m0zloFE7N$|x1>d}UPiZ@w!3 z40p65i`5uYVE8z!e-D?0kHZ=hO$ev~oyp?s^WW|>5Tvw}alF<&37fz#Wp^v(bE}`e zI-m%=Xte`G8Q!eMfFUU52Ezc}PVthM=~hOPDqoz8x9$-Agk+@feQ*{cL*2gDzIn$nDSD@U&8#o65HSeF zSzQo?c~FyreMY`}Xz4B%GkkP!$I!s8ow}X)ac#%YOiP`ZIMKgjsJ?5bUetH1KO}HJ zQ}^Uk`T+M7=WgllojYwQCTR^Y72kGMW{9fRpzsu&GoZ2Q1#UpJN!v1@+C*)+^(HM2 zw!<-sPwdlr|0Nzj#r=x2C_eWHzy1h}sPVs&u(6L99nt!QEJvwafb$KXNnyvyq{aM^ z?EQ2~Y(gxOZTRx1P{bk0_Ik0ggbAliVw;zpE)nsSHF9Y-sw|1+QH%$ark3W5h6G_h ztjZZA3M_^k=lMl6ck>}YlKs10U+lkVM&f|J7e$r4E+DE8H_?aV&$Zj{+<8HNf+e=UASF!)nBt+x~)IFy!fIiwpORYU1#KWa}{1=NO!h%6f^4t+#?agQ*Zrv*|N6B3`e zR%XD&<{5FM!f)v{hE4`zM4Zhej}z=oP$Z`Uay@kom9`y1)09{t;!UTPV$U5~3eVUX z3+FowJf*|9aVsc3N3Fo}qs?f(zioGpZ_kvSgT_-jCqECJ6I!QqE~azj?C4x8R6n{HdT>H?e+!{?7}ir#ux6kS34zscWA`Rn{|<@osU%^Sw?cDSjIrvEGit8h_y z5Z0~%Fq|J_z)*Jm_2#}hx$EoP^%swgg|0_YEdUsEt@C%m8*%1ry5Ma$tz0{N!TGCJ zj=gpL1!Kc+#iybk(_3@NoWE)e&&{Uu*Q{T-_{IyDtbXZBZ@6LQg%>Sabm5Kk1=rNV z3(voB(fJoz3GgM%MA7)}DEe<)Yc$s6`bILkRqNN`eA^`24v*n;Tjrvpu$OEhT*Yu-N02VuUhbn zD!HFJUe-}Z=`K7%dgp)t0PkEy!aa>Bn$GpVfEG90CbEedcSq5$(gs5=>gR7@!SkG? z|6j&XG>tlX`4eqaU&f#MaP#oZ>&I?$jK=k={0Lruqr<@p81BZEYZ#+Z^fa)XLEf)( zl?*v~fLURHKEeB$yuSfLcAp~OhL!8qtQuXj?ycl?_jtPROrq#)@^->^8qcTmCw>>a zGfsLIEHB_sJg9yskJ_EZ-)#Q$pLl5wf6G^Hy2*{pnF<$dSh;G=c-x!&NPxkH_XVqm zH*C7#CbHi!w*GDF7BV_se*VjBfQ+qt+XXkTUu|SLM41;;@0<7=;IG7m;h@a@>zjvH zk^6ZKlHc9y@m}F7zJDeCuX0_$-xZ|kzj^**eQ?|l-@1Vzwr1QJ0;A|A@(EuI#a5XK z(x@uR`RnCRIMEnBhrjv!IsQA(o!2uk&~x4*HH`ev;&E` zZ~~YA%qL5HzMHAF80-AYrGgau9$>wLDnq7Zyx8|nObD!1n00%dUNaGy%2dtnjvZ>J z%|F2iLQ%_Ex17vYJ9i{S7a3KlvU0vUrU1>Lm;zp*4Hh;V0vm~%Mwv0AY^Bwj$EwAb z;Z9*oWyybx`33+*7b*0NSJFHb-h#R$P@C+WO9x$rRg_E_QliMg3OdJ3K{IybIvx%d z=IW(+Z_%=5qaV{k|K(#C6bigu6vA+X843k*oP?Bx_nZdbU;#)A<_+sjJS1AcnB^kz z_6h0oX1NH@=!&}2MqIin!$b);8RM0AZfSi}Q5nU&|D*CPu3EF$MC%(Ax!Pv8h&QlSQ6PDY4#jcp)@@`S<&P+!o#D-x&X_@I0 zPmorR6ICz(LOgOrL5u@U@gHmp2=@_kqeg9IV9#Z$#n(faj|Tt;SsMyIY0&ydvv^t< zQ`3@I+@2J>@1TeEV!EtmN%4?>nkJn*DfarOe)j71^2lCgos-8M{63${LA`g}aCsH* zs`J9A?u7cKT&$RaI87Aj(6j<0ZV6rev@i~EE1tqY*h9`$(&E~v^S_&kV? zjJ#$vW}k_&W6Z-q!Qu$>&vYE9sWHHXb8U%2lx;0a2LSA^dQZ&IT<)d(G8+ zsX1VDI(_?1rF23Y_OxDY+R6dn3AWZE%ISrx;!gvA;mJGp2bbG<>=v2n=!JH`GD>wr zP(j7Q<>a!Lak9fvT%|@cOsn%UYG*q17^WHFDQwPN4y=5qBM>d@v*#qssAY$rG2Bic+GS!_waJ?k8P z-0OCJXmT+vWtT|)7+Lck^_Th?pt8C5V24htc;mr{4L4$CEaaqS5f&J52e0buq*)}q*T1nHi3WiS3gpdJloXuFNd%z3svx+_Z=?wOl&i)^a8b;TF!w2J4y^VG7M(RF-59C^e6%M6!Sj(YP>z zrZqA%N_-_I24Pm`eXSQa{(s)|_+PbA@vdrI5ArdAoiSm#L>dUF(X1?f23v?SxjD40 zm!Yh8*di3Pq3MIqfAC?DWRT&2m0RJHKFY<(!6D0rKP%6|0EpvMfQ@~gUEj{71};6x zASA?Er%+BBXE!zj9(xs4LgcGCADTF1ZAy|BA;Pd}nAcXN1?Cb@EAtlR`944yN}F0> zDZUv=(XO&C@tf6!gbbmyY!@#>Y0GMpL13cW!bK#DKP(Bb{={V16;Kq}qhOt~V*1_D zWu&e)*K_flt)3&mtmje&tO2;W)#;jb*Dwf(9X&NGV#hJcp~IH^3pyO=f!CWJT9r-p z`v2wbUEu7js{8Nfb}n1Usvyn zqBW>Sa+wY!s0J*e;Ab*jbgIdZEu=wxjHw$N(&e!O2k`Oym05(SA0QAyLxzGMaZp{2 zAxPtmbqy2)?Jpa;Da2+a95akmk#=rQNwr56*v27k;JhVEJM_jAY%5mH5Hp&x}qS&G{hkeI&<0T<;^ z#?MnYl);(=2{gyb={6??2ZAuJFN`nVe@*Z7hRAHbWR?{mBk*P2f%W+_>4l>iQ;qKw z&cFP2_p&3jW;6bGaWC5~ZX49klL%?dIb*xA!>1b?5dVztdadHtce7H8FEb=|#3D5r zN4EPJwhBlLc-Ky=RJ&eFv8gjI&Ce^T{;yrO=Lr*6txXM(`=3U44h*AdN58WahqEWt zfkXmTh_G6arQ3~T(sGb?LpGFsgC?_n9gwK4n$5Z-ZfCR)!NM{bP^6V@qEw9upV(JG z_?SSWL&D9%>|6WkUxv)6I{BDUexr*9K+9q}Pto$o|7OFQd;l}gu%*b9G&T`t_X}8e zIfE3WCnbHmgomFWRy4rQuwr}n%PApD=7WZmu>_mUTdnu(%V=0r_okD7yfyMMV$z_z zHnm*=Sk9vtxi_>(2W`en3XJ9j)4|=hfsAj9R*pKHi#7ASgPtOKX!%bKw<)BBalecH z& z6mlaexjoh=L6~njcnDb}CPV0?Dm)!94tTAU!$&kU$L->QVP3qhfsW^pW9l`?Ovqt%r?Bx4N~5W6wC5ZMf7AVq>eQ`Os!W&oVK|>k0e@+g#@8$zM;wv}2L^^j^k_pu zoLy9|wxXIbAgn+%X-7IV;zBySs;H2GHc_x^{Glv?qbu#e1$c?2?z@=~Di%sK@w)Q+ zZ*NRb7T%WZ{N^(%jZ$giJ(=SO#}S#azx)|?ppIvsx3;Wfj*3co!Z z)>y&!mC?F-j` z<+c|F-)x;19Q^i+KKPxrFZsbEt>1s}(c6AM4Sx9hhr(-L7F-cjf(0Qx;eX-E`q0tM zaD62zM^TtOEj)Ji%j?T~dcqk=*b~NK>4f-{>hUwebc6y)H3{QNEjlJ#uHs3REVXD> z7)8s%Bucn7G!BoAVnU_xULr#{HJVlqm7BV&VI{6b$A%|Uwn523s;5>oo-nE4d9GRo z5{*WBpA#(uzPy!V!j)kXQh8VnpC3k*M)iU)>Zw;&MYE|d3`hDxS}fJWc|GApNoZGi z&5V+`nczemmBaoJ+{ed6$MV0iD6CY&sNNGUpuQ`kcDOB0qMooE{{u(>+KOt7s^wY~ z4$WPd4DntH2f30YrITUI1ycefUS5p|3K{l>6*U+~_l*VNr&~dMQ@AD#%9lhz5;D=x zjRN|VgfpX3_(##KDZSw{sx#{+#zXWiik=z%9+;1a&Rh*o3Kvsr6qV@r@liGWq0klv zVbE+g18VtB_?A)-)5~NqPQrIne-ND;pI%>>ye1s(KaPIZ;)T>%37-|uL$H4~n)#BR zS`vk;W1$kHgzpUF>d}VTFq{_lRpQd8tLn^jVU@8`zA*Z~z*gpWcJ!jEQZ@)5d|gj? zs<0IFgwek<@=!#$m-dn{tqqoKq{>lzA_xyEAS8UjG(e%2SC?r3oI`DD3Vu_MmXu0* z3d{XL>Eucfo|-(L^x(v3dH@cRQneaYj!oW--78sC4g11rrLdn`2du`@x^N$5o|S-s z%4L;c&BUSL?3J(C%;BZCMfK<9n+Pvh&-Tf9cEKgj;XutgjNhj_t9ow`P|*FG$M4hm zSK{9@_^1Cu{ymd_gZwK8*`w4Mwt{T;+oO6ZB8?^dL7u~>1#7onrp)2~aQd3)+H1pW z*TmP_4=;f|2Wvu}5>ojg2+Xx>N_y6VB^C+6n!rEr$Up7~)|8dk66{Hp1i_lRQvH7v ziY!B=lb~QTR&<94gJ1>*-;U_VQD1f2WvegVv}xnk>iWwr8DD+rrdI}Wk=A?J)>p3H z!p7tHmbI6U2eslu&zALT*R5t3X={)aiAQf8-?W9pS*!KCezomgEM62uMTW+!wy^a^ zg*q6|&L|a^b;Z3^J^Iht|89e8@r8>x*lqVMemIYR7$womI))6J^@7HN=wO@5kN(r_m ztm`H+2T#65SAez?9blHSi4r;K7G0LGbQPLRB(bC%p;=?Nie=7}Afs4yx!b)zQhw-)QfkpEL0co*vf@n_g?=sd*v%)pygJfQoTVRu#Wx-mieieyh%qH`VT(MwCnv%DkOn^T$e%~6R<9lx>=e0smKnC{?Ng%41<;{(KF?gOO9jt@|| z%Ll03Q!W?g;Ekky`RiQx<4*h!s zvKiBU{C}@Rr_*3J(MgjrQxKq@IjX$Nq88vV6QSc(Z72mx9?97zif9bXIQt$b=g9Il z-Fh>RYD^9$($VE@;7r3aXQMLYu8tEEQHCJhyg$UnQig;SpJI>-h0#f~9?&3iqBI8y z>Zkc@3Jn-H^Q>Qa&{7K_2(r`LHl2&I<%!Q9+q!d8_#x90LRl{(! z&Yn`H)|z+>K<#oG{xVceHo69*E2IKN3xliLWrYjXvd1O{>6UcpfAAgNIzrqx4uKQQpDfK%jDgu9 zOWf@f?|&m%GH#Q;PFZhosqaEk)Ks&&+*)UkfV_8?^8Jx)JG9RD7=^HQV2j|ttE7s` z*Diy)Sq7VbXE`Bo;cZ6qi}os9J9LxLxy`|#=&(2wfl+MhQyDB3bv#lWL8LlMZ6XH+_JzoZ=)VYSpQ3t1;IN)CSx8^l}v&ivXsn$?Gd|`u&e@O$Ym`f>?2)%Kr&w?wP%4pZhC>8% zj{%p>DsXW};M1~pCE1T)XqFXZ!XeuwNj6FLy&~D-@3+gAjN2*=w<{yTYAV7#({h_< zoMm9ce#C?uH6__VjF{Ty*nY0>r{}XIAIuF;^;^)Y@~L^i)^r;Ii3>VIDycyNh{OqT z8GVEVSerp?DO)X>O&bR+-(|ce$wAD6ZVo@{$zYhE4HlLf@((9 zoc8>{`Ars2ka?mO8({R4?`PN;I+O1J+rg72pXM1%mQ(mn=&;Er%5_1G z$e4g%2tqB#hX|>4mrPmQ$dPF|QrdNA?SJF0VcX5ToUedD9Sw9|}R46-8BzCp%*F|Cn998uoRo$hk9<>aICRcT^NbIWW zp(1f|RfnkRK~=?|@K=5Mld$<#F)4*LA{gb(`ub*9yt=_%^VgUm()js1zWz8aFPdn7 za`V5v92K^9Pm2Al&fTcrPUAQ!{+D{#Ie|Nb(!m64tG8i zE!_~R8QMiW%3mN$1gQD?ppast!e@UoRG9rVCnI+sP2(tmq3RyIUL;XEGD#3YLYba| zB=(9>qA^cLL3RW0>Y*!SP&98>9v=4;!yuH?iG0pKR>>cgVk3GpftsQ$uradsQx*nE zVytWk*u?^4$L0K%C?u*~q^3u3!2{Gq9f&D#S#S-9%eGFYIkMFk!;-#8me0TBm^CgT z=u<#WtFQH~n8=0*>#VQIW|WQDq^cokqsz`@w$O2n=^7)Qjj`h$g35MMv|_k) z+l*0q=jtC|jed%Y-W!-$+CbV9ruz;|ggaPvk@teGU=r+bvWklGYiZ?oxav9@W6Ee@ z;}7~w^WXjGBU~tQTP-Cb@(sIIbX{?OuJ&OGyCr0D!XqKHnF6SA982m|gVsswzP_58 z4^i{mRWm}q;+SX$#EdMD86imu(Rgxuwz=)_*q0+$pU^i5pJ$(VY<7PFhynkJ$0_+I zEzDPp#EN!GuT*!{k*0f>0@wYnuK0KTU>H$jC`J1{e0p;T(sYoVgx>z=HeZTKp2~H_mJQl0VGW|sqg}6ug9A_;)*B`<~=u1 zeBr=`2`puaYKGoGrHO59Shp8qUE1i_3(<^i6zxS}jO|>Ny(xaNXKD(Q+*mk0^Gyy(y7a@}=wG!#c=p=hw zP9l&aQxN3Bo1HsGMXf)`W=q~{Ik$#g-G+0TwL;uz&}iH&k2yOf2@(;?Jz-cb@U8yj zNp_xB&aD8q(qsy(Cstqc{eJpjUkKBPe~4A~jU3Gn*+*8qi&q5Cw{LJk@p*E=G4twa ziS}i$R#`F>A2hFhY6n0udU!!qP^EA$kmc+!Ud2FyXC9dT(9!-!y~=H0kjh1pT2aKj z32yBWMxjA2_yMKAVn=F{D*roKWVNEVV%9_kGajR63)w<%)i^9XSuu*+cmoXbQ$=q> zP}Eu+OQ{fjw(3wp1zud)oCRl~*~y4fcaEF}al7&4-~x>&1{Z+zkeyKIyx0~hC+v06lI5fn&lKH>ydoK7v3FyJli@kg-=L(y$E*ZUR(*evXl)?Nz~ zfcUJ8{nllCvM?Q}%!+s4x97Icy<`8+&ulfog+fyys;rp~xH=7!kLvuhTsrEh z;y36^FJf)cnopaO_ABth_iV&P1bq&?=t%`qCDo|}#v__Cs-T}$4FzP2Oi{*kTlE28 z<*l1xuP0}+YPRs=2A<{oD?3DIF-GW&z^d6=ry0^E-?XNt{jDi!xh4976eu#)s3VGvydR;OgL%S#yLku4_|u+w{>^zbAgwE(&xJzr=@b&WH29elhOabvtdcj+37Q@vxKeEvjmpy{VFITeesz--`Kg8QrG-h5=P#hD9cr z0{r3L&(5hc>IcY9gxLw^AVQgXrv%_cOtvRoI6KUfgAK@{F@sG3ZY2+^{r7pnH=-E>fP@E`5a>HK4#Ce(Fj(YPnHPfmejXRB2qEs;;A{EGBVjIZpaLYx2_!0WKn5t8|ThF>ASd^kM<`)sIbH$!* z>PSaf(+<7!{l*sB;2;Pm1m`&SxWG%}jQbGa*X)Yw(@KjY5t4ks#67-fL)pXx&bu%) zSeyR0Og&yU(ycKq7U&MA0lCPNZV!cyc0@<8obMHxYLbVOfSRCciEeRrvM{6G%uzqv zk)}|bEzVQB_9Ei!ggn)4MVLWc;~G7OF%F)F5X#1h)$oKp31>P_$pn$g{Xr^7_Jw!I zG;{*ZNqBzfb$Yw?M*g~MMwyc_@>2 z+yBH4I6F!6G^5m@?SN}|<5ji;{x-qlcgwU(Qt~sY|8E$JapVPN=?%0pNk?T>f~S9SWC z7D{Pq9;7O0>JPjM6(^ET;*wZ9Y~S4{IYkq#2OUsc3YmFB_SLu0P0Q{=*IC0kucYBA zOu6g_%ES$`|3{$J-x|R|J6n{|JW^GMYDN4J1D~4h`6CrU$O0+QvwVyZyP%BX-WlCZ z!`n>#*!l(ZJTn+}qh#}LWfwRLf*Gwq%+MsZb__FANI2;?&Q&!em!?uYfWCyi0y(=`KOmXMZB zxCTFW3pcicWe@GM$0n(a;jOpwmJ?7CFpcUIbFqhl`e~ruh8$~yxq@p^a#%_Fq971F z>Lt^JdrMt37fr;;@2>{RjLA6|9egjXQJ4IX>{)eX(onR>J*uy^B~EUL)xBH6N8Bfh zxhLxSmL;)bl0lV$Q5<2<52#y^x5%NQN7UF5!LI()%@DW3Oc1TmqZJX)ns+b5q_EsjXnbcNOm`7AZ zi9XFYnRck??$RLmiMI(GincsEw5#z)P>UP|+bbQ7AsraD?OF+LKzM^VAEYXN2-!*!5POhxVKO$k{U&pV4(nf< zR;#l+?5XrRoHx=pmr14{naMo%@Lb5j{9OIL$k9I&G!q*NRMu<>cYT7)XB;mTit4U@AVqJ zw#yT&x1#jB7mt-rkNfK2T?g$z0^4(<3aaEfuk z(C)pu-FI%gpOVDq15pIOPW$$|D=RgI2c6HPE4CrKScO(_M(!H!+thCSqh{f3_10Ge zEp6>S7{X}M-WB1lYn%A358?Afni5O?haR~r_GL3?0D{RTAYfgOA#I9j;9J!OY> zEw8aS?(e`6>sNp$2BxEup(%F+D3_hcz`~zV(twN z0!Iksvx3x|PC55Z%KH3ZfttulDVKHoHSMF)-W_CYSnDt2qR(qr>`=Dz+QIDfsO{Kj zXpd67^=`kWH6SL)9ejDrW7q zf_O}8A#BXgNU1hjOtpb-2aS8OYkWtg8p#rs&*KA1R%&XICUuf4NN-y?$P9f)$i8(jNxcPzpf`l zz{fzrx1Yj7(jc9Vc=DJYZFGmDUq%el6gN)3>xau5P(#=_p^BlL zUlW+x5(Ya7qsovL?<&eghEieJ8|Cki-}WV*ts+B{*y;gkJqv=M#qjuitX_3eWTxYu zT>bQj;p(mr$iABOo2lJwv$8YzeODB7_0p1A;aoC`OII@MjZ0?D@yWQo2IsPUyW(1= zXZhUO^5S>@(=u~PUmS$CWyUPWqfJYvLTux4xJSzpm-V7#OTfUR%%z)>VRws9PJf-t zFWD2R_p#5yQZt}6q2ZT$bD&9#7}X!~W!tXz_u2^w!5NZK%?ps|)MmJ_1UeC&t9X7T zZ6>=xtiSX~`;Dw;AR%N|@kS#~;Z(blq3nFj8vn zR)^~MOy1T^T%L$|?}R(a-qu;fLdjItRofo~hA*j#HqBUS7iPc`IK_m85S?-ZIWiqg zQ^67>t`yMYm)XEgk)NuazL4z6I3RH~uQzlvIDqbYgLk3;w-6Xz7j0W8p2LTUa$@`K z^=mpxXh+V?CO*dq0RUz@J%N~LOkI1k-ZGt-H;$kv8Za`lNp){z?z1KyV&h{slGg`v zXMhu#_oP}ol!4)7L;VwBXTP-DJu_lfrGfLA@Hdol79wEo0E8(ZlqUC3jj2PtC!0Fvd#U_LYkneVe60$8izJY3xr*r`w}K$mGy@gQ*9ynvI8e|WCm=E5 z2r++s51mCu({&n**ZnrqOleD(1Qnc7|0vD^dO{( zB6&bM9^#3y9pp!QX!O%tbPV|^$?+35TojYa#9zs$&2I>%2n@>Lln3dYYU>koOG{cp zHqjS2fe(uKOw6OTzJ^W1U%Mf}9vjzpfakFZvy%^zq2 z+J2_4w527c-LjEoqsx|YNbHr|Nj1K9%ecRh%5KRbke&OY`>D1nNMTCSrRy($<@m*E z5WMH+AW)>azvj2iugklhi?-aYRzDzJ(dF{*t_Jtb+S;0~9Vb*C_i<6kUV_Hnz#`MN z`R%3b)=gJ#p;=edw>)@Ux^5HE^%U0dvbEzEUYxF5zjgeQ%U$%n$yvv!N73$H#P13G zzUHPN2>H#U&J_VBuOzbGrgiI2;##uHE!v$0s|#+W+|$WFi+`vu#e0os)fbduM;x0i zRVq=n+7s1E^{6jta>8*+>8OFJ;nC6bXlC!6(lOP!;kC=z-`f(N`N^ z>-l>0jp&=)9{<$%9`>rp_21dGSlGecuN^^ogYhr@Z#YKYUlx+cz-fxP?oWpLY7H=bnAe zy7lM()kkN~sZ?wAsnds-Ex+Tv5B+`5$lhD-sMJn=_C=T6{KkPztMB>Yk6w1cPk(v% zh3CEfk54@5nS(ES=f2zi?DqY4-1+glKU1zZjy`7jsi!^vJ^Szf(!R>fS?#Ak`_%9K z;75n=y)Q|h_Vi~AE*@RJ^0{Z7`@$E!_@ys9{}pR4Tz}EVtyf)h?dxuT--qrx@ZbkO zbor)FzUdY1*OcO9L3~jho^;~Gb;raD`{yL{dX6ofPKJl?r*1fQ?vZr?XX|q>WU)Z~M6_vPx~|@XX4|@thZj{fl~c{qaRt)+avmy0b3q-96NtcJuqLJL9&GUAL@q zd~$yI8MV`EgQcUcyX)oa&rFt82F64ZfAm;&_t%f_`P1+1T-+ZXQ|?QuJKu0)va!?~ z_f(p1TysXx_$d?rQQKO*;^@;~J+(2l@sggI6R+KQMtuE>{-bxFJGWe(_}U4jXSc#D z7R0lXXy@46f#s!e=Yz*z_b(GaJMOH6)4{t2o^#f-CjR!6a+tiRG7AeSRHDA}#M^g0lnlhZ@vD;6@fJsc!P&?C!Fe%7kz{=x1)nC>q`Q4wq=WE|M^y6LEzy9|3eDa=qKl{Kp|6%2=pZL;!Uw&ZK z+2_3E_g}I4#y`CI!+-VBdp>pFXTLEpZTid4|Bs*j`tZbMoB!$IzPXognsdzRYj%9_ zL)ZSz-P5KYJNNW6&OS%uHr>;0{?=3HU<&~fN{DWV4=%&31x_#Zl$#u`E&Q7>>_XB+s?=Q{m**QC&Sq+nuk|jwc4lCu#K<(WADU}yh;$%*( zC$7eom?QgfBO&NmIqW;SbarKS+RevTcsXzRzgQ0sGCfOW~(ady!DA_pQ| z{N&+}eF`thqq7Iz{F85$HZ^SOA1t2UBVtOsRD+b)?qWx!C=Vf4NF} z^qUXe_MWRIwQeMEo+E*miUia;10?b0e132=(8;!bHx_+vEWo$$0=|V8@GZQ6Z{dXj z5@ZaJAY*_883Q|cEM`2_So8^k=d}9uBd5MN#}EJF`l}~l!`eGf?P)9!=7o)^t_8JD3u>Jf)H*Gw zbvmck>AzZsPSrZ-J5uX?*}whx-oMz6|7rHK&;HBLUX!=ZR-OeRlfAB-&cJ$$&KaeI z->Ml9M2A5S7E~#cU{z1&;TP4y48(j+8;W<>%hnNcJ#91p_%nzwHbdSLGdkW9GYW4B zzThq4^ebYI>CzuND2S4xElyMx1Xi8#naU5I3h4a0iKxL(ZIXEyaX4T!F)ujzxqd%U z50R~lT};W=q5FmiJV;w6m3I4JnmWM8E-y!tPzxs6ZAn5cID*+0f!m$Cm6buvC*9y| zx4>mSm~n4N>SKYoRA8~~Ji@f7w##6A*vR~kL*6fI2wz`s7$Vl(O8-+1ht&C zRljkskY5J1@X~csQ#$$R<~wiD#YVEZqYCNt8lxwv9t*9DBd5*8HpHU}y1Vb4Je7)8 zlsl$ko?su?@}d~|RGB)~n3aXCdUpobv?JjTv)SLf7kB4+y^cxYJ4v}}c6H~x;rBfs zl+NpiDQbR3rGVJZ1PexWlArvvs7t2-bY8WS-!0+NfzuiO+vf7@ls1ERB{i}oFDkXu z_kW{yoR(r9u{bF5Y!R zJ*lIE0s_-j^IjW+D0?q9I25=+zz@{V59^(5^bh3?@GbRYd8+BdfN!av%2Q2o27XJu zkyKyVEj;=f-o~RZb{mgAFUFreAy+W!zyPRde%!`byTU@3E)e2m;uC$&!zcQjhm(BH z!%05pSo1v-Xa*@AbXFWVV^DuzRCb<1&RfV6oeT};sH0K#7&I3i83l^>R9MP+!wT6P zV&-eKgddLc&S%gx<<%HuWu@ec7G)v+XqXY*v;1P}7DW>w}u=`~7gTkbrzTutb1034UcTp8b zpP2{Z3VDtB*)x+^Tv;}MV{qwm=n@|Pq#rHA)WIZKfiQ;;nIMd_Q+zCCc-*kYB>R@! zZDb^gGrJu`VK?yQ(M*tgi5@iwEBPH_WZj|sp#HH46ON|~;>~$pB;H{B0b2<{$b!(> zM0EmHt5JksP#($|a+3$f?$=xMqH^1I9EL(P`v)mNyab*aJPnE-z=hk0V^6@WslTDx z&-~Y_l?ww%e@bZgyy3qBt^72GHh9dis%R^(7a4*>E@lLh^Kvg(R$}uDn&-0=k;GE- zUEDYZv1Ewb{myj98V(to+wi4?jI-@twg6fA!rZ?z?(*-f^1*R=Y=5kX*RS{sS#i@H zl20@(;=+TSrpU^E)l=ki9_+ZE?{C*Pw|i_`ycZ#e&U8E0wsF;S9_(>SCMrcbG7(7= zl3TfXPd2w}PcCP^yw=;Q^I+!`BRO4sYx7{&qR`!zbK=#8e%iAUrrCoBd*VIV!5RCh z3j&Prv{;vCuLAtgYozRCvv3FMeUhAcJu>@%Ko=o94|X9%DCyLM@Y}urz)4xvX_)Nt zU@uXFzVvsu=F|E(I)n%N7j48v1SJO^ejZOKpx_1wCjC?o=5)QGBv1T;b54dCQ6B6> z@FrjLBdSsR&qr_$kmm_au`YU-6I>Q>IWgy0P$id>a`P5d&~0^guJONn?9c;Tl?qpZ zwj^rL;fFV|r0&?EwgnQ@Ums3lC1K7NRRs2#rCKY@T_N|H(*hz(xfq+z1Uu1=^&|KB zcEkMRJXnYFODNWp=C;@gV5P3sZ+(g~&EEoypa2-b^g9Ey`?LQ|V00NoGzLqGn|20P z5ocu?79b#2UWlH0NRp-9k6TjzTsUcaBFb^<98_ANW76)z3kAt!`n#(xqU;Xr-PTK~ z;JWPM0p7^~M>ia+DUi<}-1lspoA1&%v><*yUEqVzVQt@5K-T5~4Bl=5j7mI7r)JE!P`?uOk*`A|eR34$RaF%vwgT(!R46 zrEJG$r9>J??pKgF)ycwgLl(p;%T&Oh-vZz{xH{fuFG+##+jbKfyK|@*ZgYN}B6L>M z`YIo#pBG&3of<_a*epylU4y{d==Z8v&NLx$bOMEPg3^i zPaY1LENq2nn?4jb<@*2u!jNvV&=yJka8ASBXRl0%P|DUANCNz$f);nb#u~B|chFUiBBtT^Fc!^RGkev)>DMMdEiBGBDNs0|_pfnK!Z16-%V9M>lf zNa>S4gxRT^B}iv;Ior(7!{PH1)}yREo+evtm()sVSI?IKbDJTi^7s}jcPNA&CADS^ zQgrOn3P8vz-~cRVQ^t8Z6l_+LaZ$mRcAQg?ZXZ?;h@+s(emP>XVIzW!F?#QSrgIYY z`7kbx7;T1-PK)lO!Tcb|?z#54&a*?L?Z&45LOZAk2B?_hRQj3f?zkW@r-M^lb`lX4 zVP4%~3@$w;;6hpgq5-OA)1$$q901ZHCwRc;M$*l#Zxt27HD6k< zKgUVu$lEf1fd7u@Byus4iuHvDmYt+|Igde>>X+t8g<%E$qE~SSQjjF<0$AfZBsJ%= z2OcSaN*b@2xo5@UU{~jU=wst2ev-7&$d-m=-FcDs} zt6TJuWCiYO?O5^9a9cUT>3Bl!aDWCqAH$R#jgMD^*QD_-ms-6B^fu) zdOLJZoc-p+$q=Re`!(Klm-T2Bv&Fe^64VVJPWlUDe)BDwZL_E&ZZ5Sr?l1}bsaQoA zI$R*HL0A;!wrl)2$ZLY!E0G9cL3R+!NR8nafdq21u!EKEO-AKL{!LyxaFmtg!j#nx z`IVhvDWJ;4)+`&l0KY$Hqd*tYh-X5Cwnz~g1M0z^$WPK@A(az@O@ao%@YZZWZnh+V z5Mlrvn$Qg38JSTR;iQsxBCT>u+)wI$oOU&pnKj=XNMeD7g5*)id@cKM-n3C^1)9t9 zc+LkkAtOuWd?RjXi02`{s%R>LK>?K!>yRZ!^MOCQQ%AZaz#Bhygcj7DcNuXpC08-# za5}Xa(xyaJ5g;16t|!n$gH_SSc$Vd7Q4VbvNlhIT!9VeAZuFhF9S zVk?eDAE1*g)fHKkp9&0I+8*lKJH4AIa2b5XM|K^m4X6p)KqM6A*0T}7J`F3nqh-} z&T7KSX+`bBRGP2M0uCgSfkIeMntxFLB=w8jn8c7RMxihXg;C5ME3Y^dkX# z!tMp)cY?^?nzLG($h2j7&}VOrZMd59hPKbQ^0wl0Zx$;m2k=yxn8JvQs7mVz#BMG- z;5Z9eU*VycaiFN?Mm~tSS~=H2ve=4aJ&O?qSoBqMH!Q^MaIGIOSTb8lx-(!yoD5z8 zecl~c#T2;KX1t8mqb#X6U#amBKw$H^EHa>vqXe3*298m-i8hfJZRm|?DnA#)f}t;B zUeMw?ek3u~KX3B7)dezKcv%+UhOqDEPOSu@T&Z#L^QHy+L1X7}nw zj25&eZ3~i+k;Mc@hR?#vCK^b>%A)^KiE!J*)gBtr+~*fb74k5TE~o?=a+;s$Pa`Bs zqZs+>ninpkIZJ5-tt;jzwG)ltsxy)?>})>?3o6q%)46C2TGLt#RdHLD)&MHxUF?At z5UK?XFMvXx=4EP8T#uiIZJP*=vTA{_pT~k@!nTPu^0X~+SsEAD@wL>voB=cFE$=2( z(cJ}BgEa4k8Z>dpZo*=s3~QB;NNM6KI;@&RE_BChOvtq3b7H_I9`dna zA8lG@p*v}17J*3U0!<3qi14_tOJg&NvQG|GS<^*RV*1L2mf5*q5fJS&_-~lFP8yp& zs?b%=vL@^yqP#^dI7m65=MMt$&5Ap^6tL=sZrpR-l8YPF81u=Y#6dtq2U`x?IO}R; zutJ2+v5X1LtQ3!w_5sRItG_?&L=a?-$F8q7#uSB)Ay2o@q2bqQUw#_xa;svESQ9$$ zA(aV zN~Wfm&hTiaxfNDim7&IwuH;}!u?8_a&^|N+b4W^zB;a?N%Rq(?#{yzq!#1TfNpyG^ z?vdxH&kUk5r`u8iXe^C8kF$FH*EVxBqYi%m z7E}{wbQse+^kJkjXpc6=^LT`CJEWpXXp_&gEMbZFA~rCyF#nv3Ucp6uv22+%Hsle#M$*p=Zq zI3QktN7<4N?}f(aJKjrSW5DjZ^T{RE0e#mR#2*$iBn$nS?=CPuQd3k6$@np_*`LNZ z+S-DrskOw;hzSL%+d1sIYzC9jB<#i+w!xyy=8o6Krm(7(`HGN=)I#iQlS;6{&`JR6 z*je#ptsP*iN&HELhKhjC)-*dco(ou)+;mU@Eo=vZ9QW59ksXl*;t1U>@j232&O1n_ zan;*senMnx+(bZd-C`B=*nk+@>IMRfk$`80OlCN*jk}}0%JDq%Oe7+zH(!p~G$-NlLvP~{ficnVN!01GXn?A*Y@9Dpb( zekJ8EfQKcu!gwr_ncH?2xQ%j^oh}p;rNEP_x5j-Yozq1l(wSw(<#A!F{&?A6Zvw*`Xit_j7AlLjLbe0coL1Jk0iIGo~XvLkGl3ZvcqEoo3 zYb6zvKLDcmgJx`4V)GLNry;9{wmkw>@1Y9UZwfikn?{o1aThjm8p7_wtRjtX$uJ`j zwnrkVB&{QVa|+iHzdcoJAYZ>{?E*QWx`J` z&6?X#NWi^hc;_5p=1GwCv!JG-2+!sNw92q6DMJSm#v@@}bC|0UH@D(Eg)tC4$?#MV zq~>(jOBZ!wXDGgCc?U<(XOBaq1&C2qHnPYlc^%~RY{DISanhHmCV~+tB@})}R}W|G zumg?>I5|htRuKL+K+KuZlFZ>WYzjlp80yoR8Zexk4HeV-`bg>zGkwA|$dYHxG@;y~ zy6zZPhsj7Q@gAK$D@GEb-J%?516b0!>cTh0rFFH3N01<*!jA7&02A9F0F}zY zQlCNSTQj0FdO>!F#A+Lxt~T!~61y7x>mspeHP)#>M}zgZM4i(y2esh1bSgjK2HrL! zoaN4?G32JTcc)3x>GU3<-|#R-Ee{9GLyagi+{$Z2b)KfBVGVgu=UL0`$EitZI~qV2 zIM$G81n2#-B=IOz&^oOFddPKt2HRF#K2_GY?C;g0F{adGMh%y0{Q1$p>MKvIELQ_wwb ziva+`4qNcTC`-8>0gby>ci z{PFa`+Pwjj0!hUMMV2&INkve2nDI%9Z%=##C{Xtt9c2tUs1FA?04OaU31A`;7m|#i z@2aF}k$q+C7}THnX^5fZAH=I$GH%Y>(EKO8xW|X>F*N%w9hK&vnB>mN;og{>?0cjp zptFuZ*7hAuFihMnOn$o0RP%E-92dGk0oj1wi5--`D?Be%xp&Dug|avKJe|j4uG2Yu=6nMm9=xBKSIQ|hBL7^jOsamuuv;UB8c7tFbb*$}4X_fBsP16iGdIqh z1$3Lq?u+qvePKxtCVio2ZmhLmlz-7^?_u3On0GMsEu`6QoBMVqP;Az^U$A8LL-;7L zmzOX25W{v{e1HfOdsO%l;|Rdj{F*S)Ak>`$=5KeycCwm_?*{c_liFi500Ol#dt=G( zOi1VvlQO_Yk8p3RFYjgub%7P2WXihbKs7B04q{sqr2k+w32|L9zg+uR798T73BvAS zabGM(xXR=a!32U$E{pkPZ(6fGQL^Zp8JRpkB@;zuIL-yf_WiSH0Ov;O~v38lMX zJ+bldbHBPcvUNBo`NuxSeka++_xmo{PX-3}+mR-bQKS0OnM8W)Od`EannXqrq9dq9 zV@rnS%^EsAGhvO>++my2`RrD5L4L@ZYzAWCb^z*IL4dO5>YIa7qKar`Z|vgfac=|( z{+qTJeHGewbhy?eKL{Rmihnc}LO)b_QOQ>KM80b9t@-548YiP;xnqj2^5D z;z$P|sWjeh{=eJX@rqv^{>kA74j=ycjo0o_aP<}M|JtwFU%cn=#}rZFqgv8cgJ_kH zhYvsSy8nCl@Yjy*`et;@x7&a4>A$@Bs~@@bdq~(t!F|5qJ@0<=J03XncYpIp_s6~d z<2U~PgKvN2KfZs{7rQ@B_>XV@(%~=u*(W|TF{`WAgAAv-61Fip5}_p~UJ6t&xV3C#_i~bS`w2cze4>E*OZ;uhfqh~>3$K;5&W~;m0NLBYqs9AoMN=Z`Dh~!b!@Ujm zAx20fF;UcXq|x3>E{*oS9QyP~qkR_vdNkV11>a&hZK}n5vdGDStW{ZArOf!vn^isjPs_Baro zeb*7>Og=!ijv%?T;4wh}lto_&SgkDB75LHI;N}v#D&$TSnGl_oDW6I76Q)ZpL=dTz zzQadC?vTKrkpqgOANANVgb8LEr4JRQ6B{|^kO2a!WixPqz$tta4%q-AcnsmFGY$^i zIvkhtW&vW;;SSiokF>IkMT~pCya{7ZU6HcUNFe|MeU!X_AOh3Ah>^b5M09V1ph+*opbqx z>xoxDJOVvjymo7P#g_HkF4=VD)=RHWFW?5<^omWFY~6JE`gK9D^7TTp@v)~8pj-$KxY%hs<;x2(VL$}L+j*|z@DtG82rl=8aX zmM?40xnlg1O_wiUe&yw_+OqbF!Qgh zE*!mR{o1voBNr{c=%S$|L&J+lhlbX!zi`pg;R_e9TXMmY3)Y{M$2HhGzV4*0TP{3l z>(!TEc+w`irf>$|qy4jJ|1JDm#J}$U){c*_zwCk{KT$bo~}7<%NF1DASihNHBRMjV1L82oW1nlB{u}Y z+2p&i9s~pY>R;mjDQLw}{F}YZz& zgRXm}xyo6WOb2W1j_Lf;XuDCG15=%ArGp@PJnY=x+_~(zbA9uXZ(%MKwJ=c~`BODC2iKu>UkbUb``#H!@<@ZWu z2Y3|pT()-0#?@DDg#irq^Rs2OZq>dTuE53G$JbWdJ=3JEi^KZr)fcZ{d&TMt)^6pB z^`6-w>QepaWur?MKKIncOHMxJS^mA99ea@L@J+JnUyQ6b{Qj(V*GB6sN&S8f0 z+%x+C*aKymN^#wd(TD>~QV8Aolj+5lW;!uXQI?`dWOkINjzLjEI@_bj;Hyf^P&E4)Vo_m$ zpU2(&!7g;fV8)6UH6yw~`DSTs4lS3oL z!7Lx2h+Nxc)h0H;Xs4(!*QH_op)Mo)0j5FhrU99>B|zZW;^JYmL6d^)=1C$pcUeg@17CWaI^L-<{%esmK|;#k#_!4-9d$H&Zai^s9BkH z$}UJ0lBuFnStv(MLQ7_n_K?g^&G*ofv1K^#zIN`ijPPg~Mk$AY3i=facXWe`5uBDS zRAFz%jgRgkAoL}+;F1>mc_Q9^)R{cSJXG%6;zF9d)gx4oOsWPxEu=uKu8ESG5b(wn ztw(tkN|GwapY9}ZtgW&<$v32_AVC|1_-mrwJD4!Dq?&Moc)OwzeF3%ksd~fRW1-vIg|V>AF(Q~_X)oh_ zW2=@1@?t;{61kL&;k%Pmn)JdI|HIi(OsR^C89TZ7X^KtBiA07#Xtul&cWc7o>V#EV z<0uw75B4MN1?U)*{0GP;hir&ROaxDi&BT9zWU0kWr{>CylNKCascvP$ZQZB@Xkk3A zO}Av}r5y`|;56o5w0TQHEb|g=GBk3}NJ|9iKzDL)z-g~6p>)Fzm9Z>Xl^CuqH}@>i z46tryJ<;4T6E2|W1e$aDRdXe^12o!W1R1jdRsOeO^E#?i8s!NkM1$b^8ta=X%c3n@ zy+yf*i><8-AcU)l$Ve&)dZJdBmG?Y7Et%h>+*~U4t@6;-q69g~h37QTPuDdfXt}=Q zhJfvx)n7Rg=oOKI?B-bM&`6I}4XFl<@&H;LObBUKNN5op8*mc_>WJ$%Ks2X6CMFTWD`j}0k8aFUE?XJ-xcEAz_=Jiuu;gqp6JRSAB@!Vc%UqXD^ew6veETSuCdhb z#)&698d#j%5Si1gk-US3Ek@PpC}Fb?i`?&>wHsnLI?U&2~6Vk0B>YRCy~h3U&KD^iy;5$q)qf zKMvWbF6;Clj>DySqWl;G?cu}yyRbur`D0}VRx7=yd)hraFdAXX(Q|zV){_+}k)^SS zt1zq4d3WN#R^LH(Y@vLE!KZt6Z{IHT?a*lM*iP^l{8HfLtq{a3`ipV(Dlc$wrO5BU zzDvNdIva4wynx}C4}ftfwrJ=U@276Qvt7D@u7jM7dL#U>sJC>38xv7AsW-HsR7qA~ zNlieq0z!+g0Z}Yv1rS>A0OBtqbP!sOICkpC+PvI5V~xiZf-ka^Q%e9!*oKB6F3C;u zN8oE@n`t)kEOluKSwpiHHW5)h0JCB;q?+vTgjhAi&tui6Zb;YYPlK=i7!3p*d<0Wq zMaAXwU;@1HgV?;jsg&LlzaJ(}j;cMm^9zZUiZy{W^Jdt5)-0VPI9)&paS%Jf5HUci ziBD6xe^~O5W(#3zX<5^TY8biBOt*srZok;PxHVB_M7rRyEUy3c0L98IJqIXnh)y>hia8gM^<1g4~JHnLyxfvyXQ&o zG=X5QBi?$Vcj66+z2R|~a=Zt2Evew!A7b&IW!+I@h68t14p|~Z2fK$u`C+a_e&!|% z#s?(F0H>Nz^T1{p5;FZ-q)|0bFo(PRi`s*{Cgs)uxitNn7jd0Bo9}i=d?3`(4_chC z$r8@9M_UBoCGHLpM$30zONq`!OQY+JvVFJH%T|;Dt(s9(D00B?$p&fb+3^$W03RJO zzTrI-v$0Z6=8gWqQQ1ye5DYXw;D?u)sh$+Y_We%9c4_o{hw$}?T!*4DVfmm&#f>N` ze<)#x1Q#Rk z>+zG2%6J-SLMaY&*$LUVtcFkJJyr%>tvSV&0*fROTLz`4Hg9*C-WqXiyU;v3J!GD0 zcGC#69+I@lZ`@;^WGq+&HPT;oTi}y=zlM+H&w}`KSsAjoC`qzL+ZGKveq1z9yPfFk z`s9`pwm3}=Aw@My7qpq+Mi;zTOFGJevz7Kb5xfc-2bQb^5-_LtqA-f zewi$ch=|@rqCkqVexWSH3h-4V$xy;!etkyi58f6k27tmJ**Vd?L3W>)iAmoYqF?j7 zFBCa7mrL>(M1N|YC@`o)@$7>Tq;>g4Nty_RNZgK0fwUKVjm)BkOO-KLI~x}0-XZhB zinD*l>=8?w18)FDp*Z_AY5wv5vCr7`G_gY6)e>i4v^11G6;Wsc>lE-Ql-bEZfUAaO zwL>|U!H(njU2_H)csXF)6W(TQLwc0NC=<&zh&3CMLXZTgAW45gl1EPjdc+?*dURJc z-wO1ELXY^8A%!ZuX+h|b6%+L6W(Cl5ujGB&GG>IflqrK3w`OG~s3dJB5$Ehw3m3>B zaginAyr^^{T`I{Joh2(pph&~!&WlQZ)gd))ShHkk{+S@AnaIgeM95ScB_~Huw^i5_ zLANLagUJV`SvgV56*U$PA{i2Csm+V1z$X4%iixlDJuz6b&|?4`H8GlKZm)DxTeccL za>r!h+a=?=O>VAUC)i;=y=^YX*|#w_`Xo(hLPCpk$csj7wFy(9ZfKx_{z%e6KIFD- zXP@kNh0FAq+jz|F7Ot=oQh+;Kjm2knpG~twag3fGra~uJ~VwR*F z1?pVJsM&rf5OrmakWs%hB;%Zw!GYJ^;mVz`{tj1egSDun5^J~~6QVQ0CCNUpTcV3y zyk>*~o}`_5ns$!wZgF74SvEOENN~%A*ufBC>mr84gbmsM7;8R(Ha( zG_b?%_+oCF1GO;(>Kcb|a&~L$X-#dBBUJrW<_t?pG^YQd6nSUel49{x3Ae%&O4E3@ zZ4%HDl>#u5hds%d0(_Gv<@;fhhoSTWubkE+R|VC<@tJnbf0TluEMp!r?j&Vd{s7FkVr%a;kBOfR*u(`=2J-?2gr z8+T9G@M+#f7&)znEHfI?sTj#*5+kznRH6zTS!|U~C3=T;~_Qs-O)kNLRIO4emWB_J7u+Uw{Ewzv9dQu1w2n0TnshY4e)b=TG-1Q1aXtP_p#ePTVb*+^Tq#R2cT`X>^* zK3hs4k*zx6vTcDh3S}G+2(?$7bYe%@c`vCNOyFd4I87btLR{lS15;cZ5DBvpx#}tN zxwC8H+8{*%AS=e)+t@7kD>zu`2vMbasJG^4I_NjTWmo(9c0)0x8Y>R}>K8xz(Zlx~ z-m!gGH*R&2HOz?H?-b#=p4N{LQwlBt1J}ZiDnk3xUK+1v2au|_r;S}L&;}Lt?$6?F zv`n6j?LNig5-aB5y++~E3kr&dytVcg@30M6e&?MT zf~%@E1pTfES`9b~odI#O2ts5T!Z2!r9Z;uzaqB0wPc2Ru(+vCF+g}m38miL5QcD8y z-0K~UxC-eYup6yNXfL@>CUyWDfiL7Wg8f2)!(<`O3wuv)MFQJYk6VL+<^*Rz-ZF@BkQ3T=i*37CcP&-+ zbi$eK;_k-7zxIR}>{bYNV2}ZtVbZ3)6Dh3QoFLX=ss?@&2qwn9v|cm>Ac@WQy2;yv z8D6QHC9L>r9Au zkT6V$CO?bS(_W^=Q{{8BxXQd}_it_!&d~sao`Xr;4K#U98l_~SQU+1Mb3JAn5+OGq zA$V?Y&P};uz;RQXp9DmMn>t>aJ@{4$nl}qqEV9gp?4VLIMhGzg+_2`S@kZz7+0tl` zNAWrB5b5u44_=^cvAmN64q^_Q07^_!P4|YWEap0A8)}AS_o$?hiS#uW!KZj z6`|?O-hf_)DFY8q^8W!5dvCh@mSN|LMGGRplVlctT<}Y(gkgb^hE{`&k0m}@D7uD* zg_pV_fc7GDL#WZ1YL#FW5lEmc-ur~&2|TZ>xUzAaOLeq*$sN?KHFQp_jAnP~$zJ#7 zJ01+SW-!!ODbT>RTQ{3~5IH7&Y3R$AY=TqxS82;vDr6fj$!s;b8Ax-$Vp;*zT51t* zvYOJx&6sKQNK}Ut5}gxtq*djgq~sRL3_Z3D3L0=IA*T?ZR$C)bvfVeMTP0J{s-o#Fq$m0p)0cZ_rkS9et`7W_i)MJB74kM4Z#K(JBDQ5JD@g6Rypqjpx7|q=STOSM^<<1XHv9 z*8GR{H%(?|Z~%UgHOvUw0jW0x0=Rl-iV&Q~)*@J1r?x-0+T>sJ{1>9+i<^gH&bVr(_z9v)R(KTp9-MdHTkTBLRmr*)pk)5P9?Uv>-^A2dD5v#yR}N zh$xBzFLg}^xjH~InvB&@_5lwkFH1U|RNMeospUbHA4xO@6&k~a9J%#7%eGeuN<-?x zb|N|#o zeY|&>6=ZI2b0WhFUAB^6VeV0p>}l<~Q==lx@9qBld2~a&>7Vt(<*tP-qf%VJQ7yc~ zkZ7#>D*y*GL7NW97S&v%aY65C%I1hLrn|1&Q=E{1EZDEtqueW^U{kMC?N!p`Q2S^Z zVeAGCaN~;|r21Ow3*n~NuBW~Txz7bo%U5wZs$JiJO>=>@MSljGR|}gEp5~9#4R#or z0#4(wd@c+u^7^B&8~Wj#!2+$hiW6ihSBwNL5|Ci}3<3IzpkbyKL?Kyyy%(Eajzf}G zwm>fny{DPIEbHtl*u5YQ$My-EX<^i{8$n#8QhgIl=Z*xK>w?G=B}VHDt$MKkTbGR7 zWt#_|jl!CY)dxX?jP+3Ha(W$PZRBi3A9W|lj+dG$BTbRANGY=4<*Ms~AT=A0Mhzwc zl!e=|BOBO|3goV6jy`K7Qm;tIk%lujGC?4Xv5n31VFoS2TxvCym5!yugJ4#~ymhl4 z=D=A{>n_beG)irSW@5w|(QMJE({56hgxnI^xrnCbMuhQZ)w2=+r`@b5Bdo(K_Vc~dw`Y|KPd&q37R$DsJ!d~bZD z!`wviD|I!HwM5s|V3JmvuWmCROk}|UiuKm!G*nhP4L85EObWcx7g9fpa>z|26ZXAi zwg#kul>RkelM1f}erOB;z_>O^6d0&0>7+Sl$OUqEp2xaViPe=N!IWKuai0)opACL< z^dlc`>c12H$g7?IzYBnb%rN2P*ry7BwA*sjuhh;bj}x}B7kjw|9wA#<_|7KL49_5xa zsQ^QYN=Pb(Hh7ISDJSM=n~Yg7$ut=^M_O~vL0A@Vg-g*o3LzSyYA0q`D+Jb(l@X~v zIX8JZkYcS%{WdAu+*oDnHWMa2_C3`eb4yA>cTa=*2TC4=jxh1@&J|E?oI{5EK(z2T zkKmFFOA47M>i`e(PTi=pGO67}6H($F$18-!;Va zz@j$V5VCSueVyVe>G0iMhJi5@6^O8%-VUY3j?$cO9wQ$*6r?7&*ahGSinf&dEdamh z5%59lw#2q5Ysn(zqybxO^g*^Ot>_3_MH*F^gyPd!p7iumsv3oLsJ`ZX;(MA31GNb6?y8nh-rbDtS>6T|F;+~l zNDfSQzLU8-3$jR>ZxR4*KJpdZllE4Zh7L!5n(p>G=&v__q0d1*!)4orq73SJR34h1 zo>L43^R0vqdH>cXzVzR2Vs9uPBdVDQv}Muh25MKF>-lI~#M3an3M$FZS4DYJN_W%j z5nY_;0+1OSk%yeu`d~~ZsRu9(qsjb>q-Dz!b!bV!x@<>0aCh3T| zN0HwQZ48%qJi?dLmo19aW+{_(^yFlSjGnrT8jkF|h!cqNG-tdR#IWNqS?F;vGe0B1dfs8#dK?m@qxmQQqlyp) zJH4f0?*+#f-&=;z_g0>&CZte=VGpA*c56pcz>T&t;F?{`G?$~Ou3_zk)ht>jt{0mF3w(uq9~k7-t>DxotAc&9hGWK+a& zfkPDny2LAA?3(1t7qubw{1zR|S3FszLMM?f<@-k%n59F*LnA|@L(3KpEnK*8(ZaYmaCqU!!qJ7x77Z<0xM(a56FMavcsEnc{I(c;C6mn>eoczE&1 z;?c#+mJBUfxMb0i#Y>hfS-NC+$;gt?CCio$EnT>D(bC0Bmn>bnba?5=($S^MhKGh1 z4lf#BJiKIh>G1II$nfa!vXP;Yg(Hhb7LP0$SvoR2GBPqcvTSr{bm8ct(Z!=nMwgBb zkB*FvjxJjUh|6ex8C5T%*fNrsO8=k!7yP&X%j1e1`Ikr}ll~)sNs|2M+Wb}J-z)!G z`EKPOD_^U8xpH6Sp2|lncUA7F+*Wye<&BlsS9Vvfscfrks$5)ITX}iqyvp+`XH>>2 z%PNa2^DED&q?MVKqbj|XN+qZ~UjAwMhvi4h-zk5q{Pprz%3mzsTfV3K@$yH?A1uGO z{AcBNm2WM-seDs;PkC4Qn(|fUt>w$hmz39)Us3-3@_FTR%FivIRvs&#QXVNUDlaGx zmgkk{mS>fxl?Td=auH-prjDw zHYo{ES5_jDl=Q=?+N5d&37}9AX)q{i4Jjmuk`gJX{QhTV?tSZzwo=&S`rSKs?#!7p zXU?2C=gb-ZjQ@uJTmPj0x_``n$$!EBg@4fB??2=J)c>*nr2ixTasT`NWByiuv%k?F z@caGm`1kucKkcXd)qcW{`S<$Y^6&A#=6~7$qJM|Kg2TJt<}df_{iS}?ukdg7Z}e|q z&+(7@^Zjf6kNIVO*e~{NU--Ehr8X~<_@`!x{tVn?nZaO zUFY_>54yeX{cg_9xLxiVx7}UsCfv9ibMJM(?S9k!x_h_#Rrf3Im)tM7cexBZ3?e`-p$DIu?>6nJo6B7a3ph>y=D;v45W)+xEzFys2|= zQ|wcb`BSY<9nk2M@JCIcp@D!!S)F?zCQ_5QFyv9I&b%PHQIq}50hyZ5&Jtp&$#7bD zq$qL)Qw>w&yrh`rrle^xqa!5Tq{O~tkf_qP_lWosMh26R10|KDq_BP}WWd4t!V2ct z^;%$skRoPSv6{l`-UG2}RyD{(!S13Z9HoUQrO0kkLnM=o3~qX5!A9*BwTG`*;K_J3 za6szSIv`aWd5_EFqGe!9+M^h#bQE)CNL#KA2YM@izmnjmbT!EBOMnzN7|Y70>klrv{{77US+>UeXhYSiR-aP`mkFp&BMg(E6%mvGu1U{sX zM|@TeKtn~$+5jY>z$`iW%8)ZymRxRZTYErBTbjEV0byromEcE8FVLQ%crm3fPbmfi zR2pM2Ab!&N0tjp$dVWe225D^uqA-Mm{A~LvOFsbX4n$#i#hEVIKo93DOAeL?nL>vw zLdsR>5aGPw0zqmqy@oegK#ZNmQIJkAS{#j(3;zhI*VR+O2}DJjYMYNQ92t@bE>BAw z&d4^55Ke|+9KZ727uq8a76OV$79q3;xkI5Nkb~qz6XS!b)94E&G~dUF7s`#ZRm=!{ z2%HxC0`Dlh%K$*NN&Fu&g>)bt*7oI)`frH!^-$AXIi8@gC&msMxYSCEkuqc?((hV~ z6uioeSRj>N3LYb}gof}f*-!=?ugFu2Nn8ksVh_;7{? z&;z0ds#F7D51pXJ=LaZ2$9f^>RrsnASlPzKn{@{+-ugJ6R3pm)mBscG+3p~KFnv5*tC1>+WDXBb!b!rEfQuenk-aXROaKYEw zoWrdM0@Q-WK5Io@`*PXH5^N-7T2%JTnIBV+EK*3f%prr-1!w-A)|OHwo5tB@s(N0u zB<4jJOPU0uUYewOUPQe_$(oOxe1(eY(X96}npM`Mf}tF`xB7lY2pcV6d)gAPcsHgZ zjRQ-97tkl_i{Qg!&(SBwyxDtUzkvEC0Lg}dSU%K7sj_OdKQM9*2*V4$eSlua*A)6c z=U(~28{+VhVlJE79z48zHwivQg)O8BhimVuhj9?|1K>;XT{lJ;YE*rP^Fq6%Y7Nie zG{s7ZPDa{L*gIqXW z?_mL3!e)hL)81(cHgz2R3}Y?x;L(Q`?&&WEEgXs+(_D>oL<`|0BQcO+$|(dzoCkEI zxY&&#WWKTEqUqarvJXY)eHlYzv`Dw|*#->suTmlkjKR4Km2WehHtHURiOV-u_^upz z27^?kan(2jgnH_@HB$|R^*E~z4=BqG|aGDwP%c&3Dhn*<~%5{@+qJXy))r`_zV zyp@kbcY`PDR_FpYI7@+Idx?ui%QHlV-cw*d>)t}Z%%r@VH>V%<3Pn<=tg<0QT83V1 zK!Y2@9EQHJ5hBS?A{Wd>NH(#K5n+N?UaRjl6iobzNh;OGQ(m1g>mpFo{P2|2W#K8Q z%yMP95=5i%s4hl{ey4R-t}Ys-_iTjX5|K+1J}I13-!aS_iup(`dmoDr-;Z$stq%ES zIpY!Hn%rm{!#nLqGC~V9OGb&u?Bo(kgUB18H|8Mlm2TWb=`}|Q>EZ^~+M2Bx&{rlW z0SF~-g+d`b!^ov0%rl^6DxOkWIL$tC2>=a@F-a>-3T#Hl8V$i?;zn^?dR(oo8yy~^ zh$d2BGqU+kXQe3&lN~BXi*?8{&i{+qkgBq{*{q3&6W!*@p&90X4u#_@jgldy@k!1n zGq$~8aWyTy8#8@1JQp$-r({+zq8Z+wDx!6B+Qx#y$;|=*FxC(z?@iXE~ZouH%xv0hx z+ncNmY}Be#ct#T7pmKwd-5(%WFSxAB6e!EKJ6H-`FF=U(*%6@TTa0r?<6GRJ_Xge! z_!g_Ni|K%L4qSAO;2gM)m0A}FT2v&I6DZ8)6F4@U8=%Zxf~8L#j1=}<+98y=VUfNK z)C2YHA-9N7cLX)HQpI77bwFy-93Xuy;-tt?kch0!K2H=4$&bnHp3XzWvLiELyp$Hm z3W)-Y;O@dICG99M45~EiZ8Hii3j4}zOfYs%FWM?rmxCzc8SooA@(1?vSzg*6+{Q{0 zB|kXI@OD^QE~alwx1xIHcDR;SRjguVRxjiPMid)zoM%%Qtgm-Wgp+k|YBgpD43BlG z`h+5b%|=UmRg;HmNC-CaZyXCDILjjbt^_f_jL23+X^FvfgRjL$%E6WE{z??+MpqpK0LBu`)*fwGb=i z0KL%L5|ydmCN29Y1;LeiL9R?x#1GV_r3wpY<5&x z=&pxS5oQGA4ahOmSB_^e{yCY-V=_0F-So^{COc659*n5v@!2ZXK(=l&2-GQpMjas& zlbO-g#R94<1Pt{+D&G@0SJ=?%&SrHdYYp9s+KvT2JhH$3iRSYQz)7ojNS~*C^&{*v zdhSHoPwPxv8~#bT#W1MYw(w`&Zw8d7iUpn^H)C&0sUi_gzq+HzU@{Qj6-!udBv|No zbKR9r032HoE=)k$OM)b zltAmES&rhQpDLq8Pmo*NG!Yr{0s(e^N^M)vDHdAe0$3fXNaZgCw3YPPrstl0{DwnM z-SF)1+OF13kACmvlTX}s|6f;#ZQZ^yeDwM|Po6yd!gY7=4>|6+;k$Qy=)do{=MkO( zMp>S}>zgM|{?$Ww5k9fQHxD5?^FXjiQm>`Uf4 zmQU>V(~nlsr*`ZMd9y}g)s`n+gRnxI&_Nn5bI~AS+fYe{{kz=FTOeRPjy01!}iqPQ@>tP73`3Lq6fd>U>m*}$W<4TN@ZIhInHG_t%V6@lr5mKRJbS|c-X27w4w0L1Z7ejemC6&tJAVO|#{-A#X( zM`BZj#tH?Sg}N1@r-X=$#_gzy?hbfGxpSgbyWX+uDpH?T6*AU}Jj_`XiSl%3(ddzT zy0a)kN%F{*$LJY6a^)gIqocDZ`j3%TVcJPrC~6%rO5=7K-CCs@M1ozU?O_r(?RZC9 zVDK&MA+3HieIn%Y(?=3lO>#@s*Wn0rt?b)7wzC@JqaD(&6tH}QAUf7IMcE3fh9K+y zxzJ^#Si7tKV8>`cql>g0Q^%j?3N3$Mb|TBnPrsOVGCn+r06LU<%65rk*cVC$e3@yD zDF=<@K!d12ru4Jbm2voSK{%{Xi%kq?ovpwTRny zdS=(&6|=jhR?O|4o|s)Rx^m6R)%K}9ui82`H?~=~9MzbY0JN3TV6|-A+P|c;W zn%TKsGhADShpB! z(`?2krl!(?Ix`#(L^F2I*+D>AcFT3a68m<3-vwNs<61lb-;Z+WjoPC>{pMUc#D_zD z3{Njo?ht7um*<+ec6w@Zd~&Xw9@{xNo@MF%MZDAZ=~-tE^|^Mw@0u+T&&1s3a5uSg zd}iVr+Q2(dnYn>W<+r&Oap_Oxb-2_X!SHHnV`}UJdpC3H(%j_uiq#OuXe8gM$(<9M z^^z7J{7{x{rLLdgl2!ZF)){DjdS>z(j&0f#iGPh%yaifZwQcg+iLKQ83iV!1ee+yu zxBbC{YA!2lg^Ai`6|&g`Uegtut3AV*hFzF0Bx-2dg=uK1Cv9^GdnDz*2=b(OD671KNwE4j2}mPP)53~^X?N~f&eTbpK3#d zR+P}<<=+0aBHc`|UmL%dSE>t#Y3MnLW{AAK=(|p_4uxZhHnL0?DnR zL#pp6y?RUa+(CZpZaPC*&;8J{)*-az8-*Agj%@-ty6I%L&vfeOcx;L?;ia_$jCOi= zBmO~UM_s`aqO((jeN!)@J*_m!|v%uHC$?k!`#H% zTkK|Cen`K>EaYFR3jdOq8}@<*Uj&h-M&k7?Xq(^pR$sv=bo;@I&>ULUBKtQg}KtQfe z9gvHnQa~;`P!$(e8AK&DSG3iD(T(ad8@XxkybbFVM$%S|X|r#uI+ENJP=|fn*v4A| zrmDjlB-K}M6m7=XcO6b02kNZ*`YaHeE!eZ(zbdiJde7TSWF1pTn0RA442gh3t_GJh zDPhBov|{xGxAgaEMZpSh#!sFtht@rqFId45PDYg83rqNLrvyUn3H8m6G4_G;XAdX6`H<>%oDAV7vlC=&9i+RPP*#U!%t^2dI3@J{` zmhL`J?P>KMQL9A=mS(z(^Hh)spe+JuHw3_KA^_bs1kfHJfc5|Zv}*#Os6+r!kK{xE z$b}*R9|?fLb^;rb@XSz5{cIGF#-&j}iknbW^Lbp0Kmnh)>-anz38X<6)f4t~@($8^ zF9n`;lmpRV81>X72i+phO$7r=+*#}b*-DJnk{I%4zR^A|o8Umq`l96J2IUWY#U488 zL1_dBsn#F|gKuf9k*$Sw%-8+42tXgu^lNv^VyJeaigncn&M#23Cgv5eLidY{8;%hD zef1i2NLnR=rFYObQuZzT4YWqLeB9byg|_}@oG?W?{2u_V4KJuP-&Aan76NDB5F}U` zk;nf~-;ujeNJKdYL5;`>5zGl1cZO#)p>!cyV7&Jy=bm zs%)^ zCgh0^R}(V@5h{i(r{f_m9Z$i7A;FMQ *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut EncoderState; + + /// Quality must be at least 2 for this bound to be correct. + fn BrotliEncoderMaxCompressedSize(input_size: usize) -> usize; + + fn BrotliEncoderSetParameter( + state: *mut EncoderState, + param: BrotliEncoderParameter, + value: u32, + ) -> BrotliBool; + + fn BrotliEncoderAttachPreparedDictionary( + state: *mut EncoderState, + dictionary: *const EncoderPreparedDictionary, + ) -> BrotliBool; + + fn BrotliEncoderCompressStream( + state: *mut EncoderState, + op: BrotliEncoderOperation, + input_len: *mut usize, + input_ptr: *mut *const u8, + out_left: *mut usize, + out_ptr: *mut *mut u8, + out_len: *mut usize, + ) -> BrotliBool; + + fn BrotliEncoderIsFinished(state: *mut EncoderState) -> BrotliBool; + + fn BrotliEncoderDestroyInstance(state: *mut EncoderState); +} + +// decompression API +extern "C" { + fn BrotliDecoderCreateInstance( + alloc: Option *mut HeapItem>, + free: Option, + opaque: *mut CustomAllocator, + ) -> *mut DecoderState; + + fn BrotliDecoderAttachDictionary( + state: *mut DecoderState, + kind: BrotliSharedDictionaryType, + len: usize, + dictionary: *const u8, + ) -> BrotliBool; + + fn BrotliDecoderDecompressStream( + state: *mut DecoderState, + input_len: *mut usize, + input_ptr: *mut *const u8, + out_left: *mut usize, + out_ptr: *mut *mut u8, + out_len: *mut usize, + ) -> BrotliStatus; + + fn BrotliDecoderIsFinished(state: *const DecoderState) -> BrotliBool; + + fn BrotliDecoderDestroyInstance(state: *mut DecoderState); +} + +/// Determines the maximum size a brotli compression could be. +/// Note: assumes the user never calls "flush" except during "finish" at the end. +pub fn compression_bound(len: usize, level: u32) -> usize { + let mut bound = unsafe { BrotliEncoderMaxCompressedSize(len) }; + if level <= 2 { + bound = bound.max(len + (len >> 10) * 8 + 64); + } + bound +} + +/// Brotli compresses a slice into a vec. +pub fn compress( + input: &[u8], + level: u32, + window_size: u32, + dictionary: Dictionary, +) -> Result, BrotliStatus> { + compress_into(input, Vec::new(), level, window_size, dictionary) +} + +/// Brotli compresses a slice, extending the `output` specified. +pub fn compress_into( + input: &[u8], + mut output: Vec, + level: u32, + window_size: u32, + dictionary: Dictionary, +) -> Result, BrotliStatus> { + let max_size = compression_bound(input.len(), level); + output.reserve_exact(max_size); + + let space = output.spare_capacity_mut(); + let count = compress_fixed(input, space, level, window_size, dictionary)?.len(); + unsafe { output.set_len(output.len() + count) } + Ok(output) +} + +/// Brotli compresses a slice into a buffer of limited capacity. +pub fn compress_fixed<'a>( + input: &'a [u8], + output: &'a mut [MaybeUninit], + level: u32, + window_size: u32, + dictionary: Dictionary, +) -> Result<&'a [u8], BrotliStatus> { + unsafe { + let state = BrotliEncoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! check { + ($ret:expr) => { + if $ret.is_err() { + BrotliEncoderDestroyInstance(state); + return Err(BrotliStatus::Failure); + } + }; + } + + check!(BrotliEncoderSetParameter( + state, + BrotliEncoderParameter::Quality, + level + )); + check!(BrotliEncoderSetParameter( + state, + BrotliEncoderParameter::WindowSize, + window_size + )); + + // attach a custom dictionary if requested + match dictionary.ptr(level) { + Ok(Some(dict)) => check!(BrotliEncoderAttachPreparedDictionary(state, dict)), + Err(status) => check!(status), + _ => {} + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.len(); + let mut out_ptr = output.as_mut_ptr() as *mut u8; + let mut out_len = out_left; + + let status = BrotliEncoderCompressStream( + state, + BrotliEncoderOperation::Finish, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + check!(status); + check!(BrotliEncoderIsFinished(state)); + BrotliEncoderDestroyInstance(state); + + // SAFETY: brotli initialized this span of bytes + let output = mem::transmute(&output[..out_len]); + Ok(output) + } +} + +/// Brotli compresses a slice into a buffer of limited capacity. +pub fn decompress(input: &[u8], dictionary: Dictionary) -> Result, BrotliStatus> { + unsafe { + let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); + let mut output: Vec = Vec::with_capacity(4 * input.len()); + + macro_rules! check { + ($ret:expr) => { + if $ret.is_err() { + BrotliDecoderDestroyInstance(state); + return Err(BrotliStatus::Failure); + } + }; + } + + // TODO: consider window and quality check? + // TODO: fuzz + if let Some(dict) = dictionary.slice() { + let attatched = BrotliDecoderAttachDictionary( + state, + BrotliSharedDictionaryType::Raw, + dict.len(), + dict.as_ptr(), + ); + check!(attatched); + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.capacity(); + let mut out_ptr = output.as_mut_ptr(); + let mut out_len = out_left; + + loop { + let status = BrotliDecoderDecompressStream( + state, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + output.set_len(out_len); + + if status == BrotliStatus::NeedsMoreOutput { + output.reserve(24 * 1024); + out_ptr = output.as_mut_ptr().add(out_len); + out_left = output.capacity() - out_len; + continue; + } + check!(status); + check!(BrotliDecoderIsFinished(state)); + break; + } + + BrotliDecoderDestroyInstance(state); + Ok(output) + } +} + +/// Brotli decompresses a slice into +pub fn decompress_fixed<'a>( + input: &'a [u8], + output: &'a mut [MaybeUninit], + dictionary: Dictionary, +) -> Result<&'a [u8], BrotliStatus> { + unsafe { + let state = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); + + macro_rules! check { + ($cond:expr) => { + if !$cond { + BrotliDecoderDestroyInstance(state); + return Err(BrotliStatus::Failure); + } + }; + } + + if let Some(dict) = dictionary.slice() { + let attatched = BrotliDecoderAttachDictionary( + state, + BrotliSharedDictionaryType::Raw, + dict.len(), + dict.as_ptr(), + ); + check!(attatched == BrotliBool::True); + } + + let mut in_len = input.len(); + let mut in_ptr = input.as_ptr(); + let mut out_left = output.len(); + let mut out_ptr = output.as_mut_ptr() as *mut u8; + let mut out_len = out_left; + + let status = BrotliDecoderDecompressStream( + state, + &mut in_len as _, + &mut in_ptr as _, + &mut out_left as _, + &mut out_ptr as _, + &mut out_len as _, + ); + check!(status == BrotliStatus::Success); + check!(BrotliDecoderIsFinished(state) == BrotliBool::True); + BrotliDecoderDestroyInstance(state); + + // SAFETY: brotli initialized this span of bytes + let output = mem::transmute(&output[..out_len]); + Ok(output) + } +} diff --git a/arbitrator/brotli/src/types.rs b/arbitrator/brotli/src/types.rs new file mode 100644 index 000000000..ace44f389 --- /dev/null +++ b/arbitrator/brotli/src/types.rs @@ -0,0 +1,102 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(dead_code)] + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +/// The default window size used during compression. +pub const DEFAULT_WINDOW_SIZE: u32 = 22; + +/// Represents the outcome of a brotli operation. +#[derive(Debug, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum BrotliStatus { + Failure, + Success, + NeedsMoreInput, + NeedsMoreOutput, +} + +impl BrotliStatus { + /// Whether the outcome of the operation was successful. + pub fn is_ok(&self) -> bool { + self == &Self::Success + } + + /// Whether the outcome of the operation was an error of any kind. + pub fn is_err(&self) -> bool { + !self.is_ok() + } +} + +/// A portable `bool`. +#[derive(PartialEq)] +#[repr(usize)] +pub(super) enum BrotliBool { + False, + True, +} + +impl BrotliBool { + /// Whether the type is `True`. This function exists since the API conflates `BrotliBool` and `BrotliStatus` at times. + pub fn is_ok(&self) -> bool { + self == &Self::True + } + + /// Whether the type is `False`. This function exists since the API conflates `BrotliBool` and `BrotliStatus` at times. + pub fn is_err(&self) -> bool { + !self.is_ok() + } +} + +/// The dictionary policy. +#[repr(C)] +pub(super) enum BrotliEncoderMode { + /// Start with an empty dictionary. + Generic, + /// Use the pre-built dictionary for text. + Text, + /// Use the pre-built dictionary for fonts. + Font, +} + +/// Configuration options for brotli compression. +#[repr(C)] +pub(super) enum BrotliEncoderParameter { + /// The dictionary policy. + Mode, + /// The brotli level. Ranges from 0 to 11. + Quality, + /// The size of the window. Defaults to 22. + WindowSize, + BlockSize, + DisableContextModeling, + SizeHint, + LargeWindowMode, + PostfixBits, + DirectDistanceCodes, + StreamOffset, +} + +/// Streaming operations for use when encoding. +#[repr(C)] +pub(super) enum BrotliEncoderOperation { + /// Produce as much output as possible. + Process, + /// Flush the contents of the encoder. + Flush, + /// Flush and finalize the contents of the encoder. + Finish, + /// Emit metadata info. + Metadata, +} + +/// Type of custom dictionary. +#[repr(C)] +pub(super) enum BrotliSharedDictionaryType { + /// LZ77 prefix dictionary + Raw, + /// Serialized dictionary + Serialized, +} diff --git a/arbitrator/brotli/src/wasmer_traits.rs b/arbitrator/brotli/src/wasmer_traits.rs new file mode 100644 index 000000000..169b2862a --- /dev/null +++ b/arbitrator/brotli/src/wasmer_traits.rs @@ -0,0 +1,29 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{dicts::Dictionary, types::BrotliStatus}; +use wasmer::FromToNativeWasmType; + +unsafe impl FromToNativeWasmType for BrotliStatus { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self::try_from(u32::from_native(native)).expect("unknown brotli status") + } + + fn to_native(self) -> i32 { + (self as u32).to_native() + } +} + +unsafe impl FromToNativeWasmType for Dictionary { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self::try_from(u32::from_native(native)).expect("unknown brotli dictionary") + } + + fn to_native(self) -> i32 { + (self as u32).to_native() + } +} diff --git a/arbitrator/caller-env/Cargo.toml b/arbitrator/caller-env/Cargo.toml new file mode 100644 index 000000000..ad4d07cca --- /dev/null +++ b/arbitrator/caller-env/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "caller-env" +version = "0.1.0" +edition.workspace = true + +[dependencies] +brotli = { path = "../brotli/", optional = true } +num_enum.workspace = true +rand_pcg = { version = "0.3.1", default-features = false } +rand = { version = "0.8.4", default-features = false } +wasmer = { path = "../tools/wasmer/lib/api", optional = true } + +[features] +default = ["brotli"] +brotli = ["dep:brotli"] +static_caller = [] +wasmer_traits = ["dep:wasmer", "brotli?/wasmer_traits"] diff --git a/arbitrator/caller-env/src/brotli/mod.rs b/arbitrator/caller-env/src/brotli/mod.rs new file mode 100644 index 000000000..2ba8c6e6f --- /dev/null +++ b/arbitrator/caller-env/src/brotli/mod.rs @@ -0,0 +1,72 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::{ExecEnv, GuestPtr, MemAccess}; +use alloc::vec::Vec; +use brotli::{BrotliStatus, Dictionary}; + +/// Brotli compresses a go slice +/// +/// The output buffer must be sufficiently large. +/// The pointers must not be null. +pub fn brotli_compress( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32, + dictionary: Dictionary, +) -> BrotliStatus { + let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); + + let result = brotli::compress_fixed( + &input, + output.spare_capacity_mut(), + level, + window_size, + dictionary, + ); + match result { + Ok(slice) => { + mem.write_slice(out_buf_ptr, slice); + mem.write_u32(out_len_ptr, slice.len() as u32); + BrotliStatus::Success + } + Err(status) => status, + } +} + +/// Brotli decompresses a go slice using a custom dictionary. +/// +/// # Safety +/// +/// The output buffer must be sufficiently large. +/// The pointers must not be null. +pub fn brotli_decompress( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + dictionary: Dictionary, +) -> BrotliStatus { + let input = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let mut output = Vec::with_capacity(mem.read_u32(out_len_ptr) as usize); + + let result = brotli::decompress_fixed(&input, output.spare_capacity_mut(), dictionary); + match result { + Ok(slice) => { + mem.write_slice(out_buf_ptr, slice); + mem.write_u32(out_len_ptr, slice.len() as u32); + BrotliStatus::Success + } + Err(status) => status, + } +} diff --git a/arbitrator/caller-env/src/guest_ptr.rs b/arbitrator/caller-env/src/guest_ptr.rs new file mode 100644 index 000000000..cbef490c6 --- /dev/null +++ b/arbitrator/caller-env/src/guest_ptr.rs @@ -0,0 +1,49 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use core::ops::{Add, AddAssign, Deref}; + +/// Represents a pointer to a Guest WASM's memory. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(transparent)] +pub struct GuestPtr(pub u32); + +impl Add for GuestPtr { + type Output = Self; + + fn add(self, rhs: u32) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl AddAssign for GuestPtr { + fn add_assign(&mut self, rhs: u32) { + *self = *self + rhs; + } +} + +impl From for u32 { + fn from(value: GuestPtr) -> Self { + value.0 + } +} + +impl From for u64 { + fn from(value: GuestPtr) -> Self { + value.0.into() + } +} + +impl Deref for GuestPtr { + type Target = u32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl GuestPtr { + pub fn to_u64(self) -> u64 { + self.into() + } +} diff --git a/arbitrator/caller-env/src/lib.rs b/arbitrator/caller-env/src/lib.rs new file mode 100644 index 000000000..ba3874919 --- /dev/null +++ b/arbitrator/caller-env/src/lib.rs @@ -0,0 +1,67 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![cfg_attr(target_arch = "wasm32", no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use rand_pcg::Pcg32; + +pub use guest_ptr::GuestPtr; +pub use wasip1_stub::Errno; + +#[cfg(feature = "static_caller")] +pub mod static_caller; + +#[cfg(feature = "wasmer_traits")] +pub mod wasmer_traits; + +#[cfg(feature = "brotli")] +pub mod brotli; + +mod guest_ptr; +pub mod wasip1_stub; + +/// Initializes a deterministic, psuedo-random number generator with a fixed seed. +pub fn create_pcg() -> Pcg32 { + const PCG_INIT_STATE: u64 = 0xcafef00dd15ea5e5; + const PCG_INIT_STREAM: u64 = 0xa02bdbf7bb3c0a7; + Pcg32::new(PCG_INIT_STATE, PCG_INIT_STREAM) +} + +/// Access Guest memory. +pub trait MemAccess { + fn read_u8(&self, ptr: GuestPtr) -> u8; + + fn read_u16(&self, ptr: GuestPtr) -> u16; + + fn read_u32(&self, ptr: GuestPtr) -> u32; + + fn read_u64(&self, ptr: GuestPtr) -> u64; + + fn write_u8(&mut self, ptr: GuestPtr, x: u8); + + fn write_u16(&mut self, ptr: GuestPtr, x: u16); + + fn write_u32(&mut self, ptr: GuestPtr, x: u32); + + fn write_u64(&mut self, ptr: GuestPtr, x: u64); + + fn read_slice(&self, ptr: GuestPtr, len: usize) -> Vec; + + fn read_fixed(&self, ptr: GuestPtr) -> [u8; N]; + + fn write_slice(&mut self, ptr: GuestPtr, data: &[u8]); +} + +/// Update the Host environment. +pub trait ExecEnv { + fn advance_time(&mut self, ns: u64); + + fn get_time(&self) -> u64; + + fn next_rand_u32(&mut self) -> u32; + + fn print_string(&mut self, message: &[u8]); +} diff --git a/arbitrator/caller-env/src/static_caller.rs b/arbitrator/caller-env/src/static_caller.rs new file mode 100644 index 000000000..46a2a3f48 --- /dev/null +++ b/arbitrator/caller-env/src/static_caller.rs @@ -0,0 +1,119 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{create_pcg, ExecEnv, GuestPtr, MemAccess}; +use alloc::vec::Vec; +use rand::RngCore; +use rand_pcg::Pcg32; + +extern crate alloc; + +static mut TIME: u64 = 0; +static mut RNG: Option = None; + +pub struct StaticMem; +pub struct StaticExecEnv; + +pub static mut STATIC_MEM: StaticMem = StaticMem; +pub static mut STATIC_ENV: StaticExecEnv = StaticExecEnv; + +extern "C" { + fn wavm_caller_load8(ptr: GuestPtr) -> u8; + fn wavm_caller_load32(ptr: GuestPtr) -> u32; + fn wavm_caller_store8(ptr: GuestPtr, val: u8); + fn wavm_caller_store32(ptr: GuestPtr, val: u32); +} + +impl MemAccess for StaticMem { + fn read_u8(&self, ptr: GuestPtr) -> u8 { + unsafe { wavm_caller_load8(ptr) } + } + + fn read_u16(&self, ptr: GuestPtr) -> u16 { + let lsb = self.read_u8(ptr); + let msb = self.read_u8(ptr + 1); + (msb as u16) << 8 | (lsb as u16) + } + + fn read_u32(&self, ptr: GuestPtr) -> u32 { + unsafe { wavm_caller_load32(ptr) } + } + + fn read_u64(&self, ptr: GuestPtr) -> u64 { + let lsb = self.read_u32(ptr); + let msb = self.read_u32(ptr + 4); + (msb as u64) << 32 | (lsb as u64) + } + + fn write_u8(&mut self, ptr: GuestPtr, x: u8) { + unsafe { wavm_caller_store8(ptr, x) } + } + + fn write_u16(&mut self, ptr: GuestPtr, x: u16) { + self.write_u8(ptr, (x & 0xff) as u8); + self.write_u8(ptr + 1, ((x >> 8) & 0xff) as u8); + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) { + unsafe { wavm_caller_store32(ptr, x) } + } + + fn write_u64(&mut self, ptr: GuestPtr, x: u64) { + self.write_u32(ptr, (x & 0xffffffff) as u32); + self.write_u32(ptr + 4, ((x >> 32) & 0xffffffff) as u32); + } + + fn read_slice(&self, mut ptr: GuestPtr, mut len: usize) -> Vec { + let mut data = Vec::with_capacity(len); + if len == 0 { + return data; + } + while len >= 4 { + data.extend(self.read_u32(ptr).to_le_bytes()); + ptr += 4; + len -= 4; + } + for _ in 0..len { + data.push(self.read_u8(ptr)); + ptr += 1; + } + data + } + + fn read_fixed(&self, ptr: GuestPtr) -> [u8; N] { + self.read_slice(ptr, N).try_into().unwrap() + } + + fn write_slice(&mut self, mut ptr: GuestPtr, mut src: &[u8]) { + while src.len() >= 4 { + let mut arr = [0; 4]; + arr.copy_from_slice(&src[..4]); + self.write_u32(ptr, u32::from_le_bytes(arr)); + ptr += 4; + src = &src[4..]; + } + for &byte in src { + self.write_u8(ptr, byte); + ptr += 1; + } + } +} + +impl ExecEnv for StaticExecEnv { + fn print_string(&mut self, _data: &[u8]) { + // printing is done by arbitrator machine host_call_hook + // capturing the fd_write call directly + } + + fn get_time(&self) -> u64 { + unsafe { TIME } + } + + fn advance_time(&mut self, delta: u64) { + unsafe { TIME += delta } + } + + fn next_rand_u32(&mut self) -> u32 { + unsafe { RNG.get_or_insert_with(create_pcg) }.next_u32() + } +} diff --git a/arbitrator/caller-env/src/wasip1_stub.rs b/arbitrator/caller-env/src/wasip1_stub.rs new file mode 100644 index 000000000..2f07cd7e5 --- /dev/null +++ b/arbitrator/caller-env/src/wasip1_stub.rs @@ -0,0 +1,407 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//! A stub impl of [WASI Preview 1][Wasi] for proving fraud. +//! +//! [Wasi]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md + +#![allow(clippy::too_many_arguments)] + +use crate::{ExecEnv, GuestPtr, MemAccess}; + +#[repr(transparent)] +pub struct Errno(pub(crate) u16); + +pub const ERRNO_SUCCESS: Errno = Errno(0); +pub const ERRNO_BADF: Errno = Errno(8); +pub const ERRNO_INVAL: Errno = Errno(28); + +/// Writes the number and total size of args passed by the OS. +/// Note that this currently consists of just the program name `bin`. +pub fn args_sizes_get( + mem: &mut M, + _: &mut E, + length_ptr: GuestPtr, + data_size_ptr: GuestPtr, +) -> Errno { + mem.write_u32(length_ptr, 1); + mem.write_u32(data_size_ptr, 4); + ERRNO_SUCCESS +} + +/// Writes the args passed by the OS. +/// Note that this currently consists of just the program name `bin`. +pub fn args_get( + mem: &mut M, + _: &mut E, + argv_buf: GuestPtr, + data_buf: GuestPtr, +) -> Errno { + mem.write_u32(argv_buf, data_buf.into()); + mem.write_u32(data_buf, 0x6E6962); // "bin\0" + ERRNO_SUCCESS +} + +/// Writes the number and total size of OS environment variables. +/// Note that none exist in Nitro. +pub fn environ_sizes_get( + mem: &mut M, + _env: &mut E, + length_ptr: GuestPtr, + data_size_ptr: GuestPtr, +) -> Errno { + mem.write_u32(length_ptr, 0); + mem.write_u32(data_size_ptr, 0); + ERRNO_SUCCESS +} + +/// Writes the number and total size of OS environment variables. +/// Note that none exist in Nitro. +pub fn environ_get( + _: &mut M, + _: &mut E, + _: GuestPtr, + _: GuestPtr, +) -> Errno { + ERRNO_SUCCESS +} + +/// Writes to the given file descriptor. +/// Note that we only support stdout and stderr. +pub fn fd_write( + mem: &mut M, + env: &mut E, + fd: u32, + iovecs_ptr: GuestPtr, + iovecs_len: u32, + ret_ptr: GuestPtr, +) -> Errno { + if fd != 1 && fd != 2 { + return ERRNO_BADF; + } + let mut size = 0; + for i in 0..iovecs_len { + let ptr = iovecs_ptr + i * 8; + let len = mem.read_u32(ptr + 4); + let ptr = mem.read_u32(ptr); // TODO: string might be split across utf-8 character boundary + let data = mem.read_slice(GuestPtr(ptr), len as usize); + env.print_string(&data); + size += len; + } + mem.write_u32(ret_ptr, size); + ERRNO_SUCCESS +} + +/// Closes the given file descriptor. Unsupported. +pub fn fd_close(_: &mut M, _: &mut E, _: u32) -> Errno { + ERRNO_BADF +} + +/// Reads from the given file descriptor. Unsupported. +pub fn fd_read( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Reads the contents of a directory. Unsupported. +pub fn fd_readdir( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, + _: u64, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Syncs a file to disk. Unsupported. +pub fn fd_sync(_: &mut M, _: &mut E, _: u32) -> Errno { + ERRNO_SUCCESS +} + +/// Move within a file. Unsupported. +pub fn fd_seek( + _: &mut M, + _: &mut E, + _fd: u32, + _offset: u64, + _whence: u8, + _filesize: u32, +) -> Errno { + ERRNO_BADF +} + +/// Syncs file contents to disk. Unsupported. +pub fn fd_datasync(_: &mut M, _: &mut E, _fd: u32) -> Errno { + ERRNO_BADF +} + +/// Retrieves attributes about a file descriptor. Unsupported. +pub fn fd_fdstat_get(_: &mut M, _: &mut E, _: u32, _: u32) -> Errno { + ERRNO_INVAL +} + +/// Sets the attributes of a file descriptor. Unsupported. +pub fn fd_fdstat_set_flags( + _: &mut M, + _: &mut E, + _: u32, + _: u32, +) -> Errno { + ERRNO_INVAL +} + +/// Opens the file or directory at the given path. Unsupported. +pub fn path_open( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + _: u64, + _: u64, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Creates a directory. Unsupported. +pub fn path_create_directory( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Unlinks a directory. Unsupported. +pub fn path_remove_directory( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Resolves a symbolic link. Unsupported. +pub fn path_readlink( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Moves a file. Unsupported. +pub fn path_rename( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about an open file. Unsupported. +pub fn path_filestat_get( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Unlinks the file at the given path. Unsupported. +pub fn path_unlink_file( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about a file. Unsupported. +pub fn fd_prestat_get(_: &mut M, _: &mut E, _: u32, _: u32) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about a directory. Unsupported. +pub fn fd_prestat_dir_name( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about a file. Unsupported. +pub fn fd_filestat_get( + _: &mut M, + _: &mut E, + _fd: u32, + _filestat: u32, +) -> Errno { + ERRNO_BADF +} + +/// Sets the size of an open file. Unsupported. +pub fn fd_filestat_set_size( + _: &mut M, + _: &mut E, + _fd: u32, + _: u64, +) -> Errno { + ERRNO_BADF +} + +/// Peaks within a descriptor without modifying its state. Unsupported. +pub fn fd_pread( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, + _: u64, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Writes to a descriptor without modifying the current offset. Unsupported. +pub fn fd_pwrite( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, + _: u64, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Accepts a new connection. Unsupported. +pub fn sock_accept( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Shuts down a socket. Unsupported. +pub fn sock_shutdown(_: &mut M, _: &mut E, _: u32, _: u32) -> Errno { + ERRNO_BADF +} + +/// Yields execution to the OS scheduler. Effectively does nothing in Nitro due to the lack of threads. +pub fn sched_yield(_: &mut M, _: &mut E) -> Errno { + ERRNO_SUCCESS +} + +/// 10ms in ns +static TIME_INTERVAL: u64 = 10_000_000; + +/// Retrieves the time in ns of the given clock. +/// Note that in Nitro, all clocks point to the same deterministic counter that advances 10ms whenever +/// this function is called. +pub fn clock_time_get( + mem: &mut M, + env: &mut E, + _clock_id: u32, + _precision: u64, + time_ptr: GuestPtr, +) -> Errno { + env.advance_time(TIME_INTERVAL); + mem.write_u64(time_ptr, env.get_time()); + ERRNO_SUCCESS +} + +/// Fills a slice with psuedo-random bytes. +/// Note that in Nitro, the bytes are deterministically generated from a common seed. +pub fn random_get( + mem: &mut M, + env: &mut E, + mut buf: GuestPtr, + mut len: u32, +) -> Errno { + while len >= 4 { + let next_rand = env.next_rand_u32(); + mem.write_u32(buf, next_rand); + buf += 4; + len -= 4; + } + if len > 0 { + let mut rem = env.next_rand_u32(); + for _ in 0..len { + mem.write_u8(buf, rem as u8); + buf += 1; + rem >>= 8; + } + } + ERRNO_SUCCESS +} + +/// Poll for events. +/// Note that we always simulate a timeout and skip all others. +pub fn poll_oneoff( + mem: &mut M, + env: &mut E, + in_subs: GuestPtr, + out_evt: GuestPtr, + num_subscriptions: u32, + num_events_ptr: GuestPtr, +) -> Errno { + // simulate the passage of time each poll request + env.advance_time(TIME_INTERVAL); + + const SUBSCRIPTION_SIZE: u32 = 48; // user data + 40-byte union + for index in 0..num_subscriptions { + let subs_base = in_subs + (SUBSCRIPTION_SIZE * index); + let subs_type = mem.read_u32(subs_base + 8); + if subs_type != 0 { + // not a clock subscription type + continue; + } + let user_data = mem.read_u32(subs_base); + mem.write_u32(out_evt, user_data); + mem.write_u32(out_evt + 8, subs_type); + mem.write_u32(num_events_ptr, 1); + return ERRNO_SUCCESS; + } + ERRNO_INVAL +} diff --git a/arbitrator/caller-env/src/wasmer_traits.rs b/arbitrator/caller-env/src/wasmer_traits.rs new file mode 100644 index 000000000..babc22c6f --- /dev/null +++ b/arbitrator/caller-env/src/wasmer_traits.rs @@ -0,0 +1,35 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{Errno, GuestPtr}; +use wasmer::{FromToNativeWasmType, WasmPtr}; + +unsafe impl FromToNativeWasmType for GuestPtr { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self(u32::from_native(native)) + } + + fn to_native(self) -> i32 { + self.0.to_native() + } +} + +unsafe impl FromToNativeWasmType for Errno { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self(u16::from_native(native)) + } + + fn to_native(self) -> i32 { + self.0.to_native() + } +} + +impl From for WasmPtr { + fn from(value: GuestPtr) -> Self { + WasmPtr::new(value.0) + } +} diff --git a/arbitrator/cbindgen.toml b/arbitrator/cbindgen.toml deleted file mode 100644 index 08094f28f..000000000 --- a/arbitrator/cbindgen.toml +++ /dev/null @@ -1 +0,0 @@ -language = "C" diff --git a/arbitrator/jit/Cargo.toml b/arbitrator/jit/Cargo.toml index 75b3e3a74..fb49b871b 100644 --- a/arbitrator/jit/Cargo.toml +++ b/arbitrator/jit/Cargo.toml @@ -5,9 +5,13 @@ edition = "2021" [dependencies] arbutil = { path = "../arbutil/" } -wasmer = "3.1.0" -wasmer-compiler-cranelift = "3.1.0" -wasmer-compiler-llvm = { version = "3.1.0", optional = true } +brotli = { path = "../brotli/", features = ["wasmer_traits"] } +caller-env = { path = "../caller-env/", features = ["wasmer_traits"] } +prover = { path = "../prover/", default-features = false, features = ["native"] } +stylus = { path = "../stylus/", default-features = false } +wasmer = { path = "../tools/wasmer/lib/api/" } +wasmer-compiler-llvm = { path = "../tools/wasmer/lib/compiler-llvm/", optional = true } +wasmer-compiler-cranelift = { path = "../tools/wasmer/lib/compiler-cranelift/" } eyre = "0.6.5" parking_lot = "0.12.1" rand = { version = "0.8.4", default-features = false } @@ -17,7 +21,7 @@ hex = "0.4.3" structopt = "0.3.26" sha3 = "0.9.1" libc = "0.2.132" -ouroboros = "0.16.0" +sha2 = "0.9.9" [features] llvm = ["dep:wasmer-compiler-llvm"] diff --git a/arbitrator/jit/build.rs b/arbitrator/jit/build.rs deleted file mode 100644 index e18155017..000000000 --- a/arbitrator/jit/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - // Tell Cargo that if the given file changes, to rerun this build script. - println!("cargo:rustc-link-search=../target/lib/"); - println!("cargo:rustc-link-lib=static=brotlienc-static"); - println!("cargo:rustc-link-lib=static=brotlidec-static"); - println!("cargo:rustc-link-lib=static=brotlicommon-static"); -} diff --git a/arbitrator/jit/src/arbcompress.rs b/arbitrator/jit/src/arbcompress.rs index 469b21895..8000d51b2 100644 --- a/arbitrator/jit/src/arbcompress.rs +++ b/arbitrator/jit/src/arbcompress.rs @@ -1,93 +1,41 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use crate::{gostack::GoStack, machine::WasmEnvMut}; - -extern "C" { - pub fn BrotliDecoderDecompress( - encoded_size: usize, - encoded_buffer: *const u8, - decoded_size: *mut usize, - decoded_buffer: *mut u8, - ) -> u32; - - pub fn BrotliEncoderCompress( - quality: u32, - lgwin: u32, - mode: u32, - input_size: usize, - input_buffer: *const u8, - encoded_size: *mut usize, - encoded_buffer: *mut u8, - ) -> u32; -} - -const BROTLI_MODE_GENERIC: u32 = 0; -const BROTLI_RES_SUCCESS: u32 = 1; - -pub fn brotli_compress(mut env: WasmEnvMut, sp: u32) { - let (sp, _) = GoStack::new(sp, &mut env); - - //(inBuf []byte, outBuf []byte, level int, windowSize int) int - let in_buf_ptr = sp.read_u64(0); - let in_buf_len = sp.read_u64(1); - let out_buf_ptr = sp.read_u64(3); - let out_buf_len = sp.read_u64(4); - let level = sp.read_u64(6) as u32; - let windowsize = sp.read_u64(7) as u32; - let output_arg = 8; - - let in_slice = sp.read_slice(in_buf_ptr, in_buf_len); - let mut output = vec![0u8; out_buf_len as usize]; - let mut output_len = out_buf_len as usize; - - let res = unsafe { - BrotliEncoderCompress( - level, - windowsize, - BROTLI_MODE_GENERIC, - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ) +use crate::caller_env::{JitEnv, JitExecEnv}; +use crate::machine::Escape; +use crate::machine::WasmEnvMut; +use brotli::{BrotliStatus, Dictionary}; +use caller_env::{self, GuestPtr}; + +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + $( + #[allow(clippy::too_many_arguments)] + pub fn $func_name(mut src: WasmEnvMut, $($arg_name : $arg_type),*) -> Result<$return_type, Escape> { + let (mut mem, wenv) = src.jit_env(); + + Ok(caller_env::brotli::$func_name(&mut mem, &mut JitExecEnv { wenv }, $($arg_name),*)) + } + )* }; - - if (res != BROTLI_RES_SUCCESS) || (output_len as u64 > out_buf_len) { - sp.write_u64(output_arg, u64::MAX); - return; - } - sp.write_slice(out_buf_ptr, &output[..output_len]); - sp.write_u64(output_arg, output_len as u64); } -pub fn brotli_decompress(mut env: WasmEnvMut, sp: u32) { - let (sp, _) = GoStack::new(sp, &mut env); - - //(inBuf []byte, outBuf []byte) int - let in_buf_ptr = sp.read_u64(0); - let in_buf_len = sp.read_u64(1); - let out_buf_ptr = sp.read_u64(3); - let out_buf_len = sp.read_u64(4); - let output_arg = 6; - - let in_slice = sp.read_slice(in_buf_ptr, in_buf_len); - let mut output = vec![0u8; out_buf_len as usize]; - let mut output_len = out_buf_len as usize; - - let res = unsafe { - BrotliDecoderDecompress( - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ) - }; - - if (res != BROTLI_RES_SUCCESS) || (output_len as u64 > out_buf_len) { - sp.write_u64(output_arg, u64::MAX); - return; - } - sp.write_slice(out_buf_ptr, &output[..output_len]); - sp.write_u64(output_arg, output_len as u64); +wrap! { + fn brotli_compress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32, + dictionary: Dictionary + ) -> BrotliStatus; + + fn brotli_decompress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + dictionary: Dictionary + ) -> BrotliStatus } diff --git a/arbitrator/jit/src/caller_env.rs b/arbitrator/jit/src/caller_env.rs new file mode 100644 index 000000000..f4fbff10a --- /dev/null +++ b/arbitrator/jit/src/caller_env.rs @@ -0,0 +1,185 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::machine::{WasmEnv, WasmEnvMut}; +use arbutil::{Bytes20, Bytes32}; +use caller_env::{ExecEnv, GuestPtr, MemAccess}; +use rand::RngCore; +use rand_pcg::Pcg32; +use std::{ + cmp::Ordering, + collections::{BTreeSet, BinaryHeap}, + fmt::Debug, + mem::{self, MaybeUninit}, +}; +use wasmer::{Memory, MemoryView, StoreMut, WasmPtr}; + +pub struct JitMemAccess<'s> { + pub memory: Memory, + pub store: StoreMut<'s>, +} + +pub struct JitExecEnv<'s> { + pub wenv: &'s mut WasmEnv, +} + +pub(crate) trait JitEnv<'a> { + fn jit_env(&mut self) -> (JitMemAccess<'_>, &mut WasmEnv); +} + +impl<'a> JitEnv<'a> for WasmEnvMut<'a> { + fn jit_env(&mut self) -> (JitMemAccess<'_>, &mut WasmEnv) { + let memory = self.data().memory.clone().unwrap(); + let (wenv, store) = self.data_and_store_mut(); + (JitMemAccess { memory, store }, wenv) + } +} + +impl<'s> JitMemAccess<'s> { + fn view(&self) -> MemoryView { + self.memory.view(&self.store) + } + + pub fn write_bytes32(&mut self, ptr: GuestPtr, val: Bytes32) { + self.write_slice(ptr, val.as_slice()) + } + + pub fn read_bytes20(&mut self, ptr: GuestPtr) -> Bytes20 { + self.read_fixed(ptr).into() + } + + pub fn read_bytes32(&mut self, ptr: GuestPtr) -> Bytes32 { + self.read_fixed(ptr).into() + } +} + +impl MemAccess for JitMemAccess<'_> { + fn read_u8(&self, ptr: GuestPtr) -> u8 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn read_u16(&self, ptr: GuestPtr) -> u16 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn read_u32(&self, ptr: GuestPtr) -> u32 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn read_u64(&self, ptr: GuestPtr) -> u64 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn write_u8(&mut self, ptr: GuestPtr, x: u8) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn write_u16(&mut self, ptr: GuestPtr, x: u16) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn write_u64(&mut self, ptr: GuestPtr, x: u64) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn read_slice(&self, ptr: GuestPtr, len: usize) -> Vec { + let mut data: Vec> = Vec::with_capacity(len); + // SAFETY: read_uninit fills all available space + unsafe { + data.set_len(len); + self.view() + .read_uninit(ptr.into(), &mut data) + .expect("bad read"); + mem::transmute(data) + } + } + + fn read_fixed(&self, ptr: GuestPtr) -> [u8; N] { + self.read_slice(ptr, N).try_into().unwrap() + } + + fn write_slice(&mut self, ptr: GuestPtr, src: &[u8]) { + self.view().write(ptr.into(), src).unwrap(); + } +} + +impl ExecEnv for JitExecEnv<'_> { + fn advance_time(&mut self, ns: u64) { + self.wenv.go_state.time += ns; + } + + fn get_time(&self) -> u64 { + self.wenv.go_state.time + } + + fn next_rand_u32(&mut self) -> u32 { + self.wenv.go_state.rng.next_u32() + } + + fn print_string(&mut self, bytes: &[u8]) { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => eprintln!("JIT: WASM says: {s}"), // TODO: this adds too many newlines since go calls this in chunks + Err(e) => { + let bytes = e.as_bytes(); + eprintln!("Go string {} is not valid utf8: {e:?}", hex::encode(bytes)); + } + } + } +} + +pub struct GoRuntimeState { + /// An increasing clock used when Go asks for time, measured in nanoseconds. + pub time: u64, + /// Deterministic source of random data. + pub rng: Pcg32, +} + +impl Default for GoRuntimeState { + fn default() -> Self { + Self { + time: 0, + rng: caller_env::create_pcg(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TimeoutInfo { + pub time: u64, + pub id: u32, +} + +impl Ord for TimeoutInfo { + fn cmp(&self, other: &Self) -> Ordering { + other + .time + .cmp(&self.time) + .then_with(|| other.id.cmp(&self.id)) + } +} + +impl PartialOrd for TimeoutInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Default, Debug)] +pub struct TimeoutState { + /// Contains tuples of (time, id) + pub times: BinaryHeap, + pub pending_ids: BTreeSet, + pub next_id: u32, +} diff --git a/arbitrator/jit/src/color.rs b/arbitrator/jit/src/color.rs deleted file mode 100644 index 05b51d73b..000000000 --- a/arbitrator/jit/src/color.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2020-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -#![allow(dead_code)] - -use std::fmt; - -pub const RED: &str = "\x1b[31;1m"; -pub const BLUE: &str = "\x1b[34;1m"; -pub const YELLOW: &str = "\x1b[33;1m"; -pub const PINK: &str = "\x1b[38;5;161;1m"; -pub const MINT: &str = "\x1b[38;5;48;1m"; -pub const GREY: &str = "\x1b[90m"; -pub const RESET: &str = "\x1b[0;0m"; - -pub const LIME: &str = "\x1b[38;5;119;1m"; -pub const LAVENDER: &str = "\x1b[38;5;183;1m"; -pub const MAROON: &str = "\x1b[38;5;124;1m"; -pub const ORANGE: &str = "\x1b[38;5;202;1m"; - -pub fn color(color: &str, text: S) -> String { - format!("{}{}{}", color, text, RESET) -} - -/// Colors text red. -pub fn red(text: S) -> String { - color(RED, text) -} - -/// Colors text blue. -pub fn blue(text: S) -> String { - color(BLUE, text) -} - -/// Colors text yellow. -pub fn yellow(text: S) -> String { - color(YELLOW, text) -} - -/// Colors text pink. -pub fn pink(text: S) -> String { - color(PINK, text) -} - -/// Colors text grey. -pub fn grey(text: S) -> String { - color(GREY, text) -} - -/// Colors text lavender. -pub fn lavender(text: S) -> String { - color(LAVENDER, text) -} - -/// Colors text mint. -pub fn mint(text: S) -> String { - color(MINT, text) -} - -/// Colors text lime. -pub fn lime(text: S) -> String { - color(LIME, text) -} - -/// Colors text orange. -pub fn orange(text: S) -> String { - color(ORANGE, text) -} - -/// Colors text maroon. -pub fn maroon(text: S) -> String { - color(MAROON, text) -} - -/// Color a bool one of two colors depending on its value. -pub fn color_if(cond: bool, true_color: &str, false_color: &str) -> String { - match cond { - true => color(true_color, &format!("{cond}")), - false => color(false_color, &format!("{cond}")), - } -} - -/// Color a bool if true -pub fn when(cond: bool, text: S, when_color: &str) -> String { - match cond { - true => color(when_color, text), - false => format!("{text}"), - } -} diff --git a/arbitrator/jit/src/gostack.rs b/arbitrator/jit/src/gostack.rs deleted file mode 100644 index bf7ac4767..000000000 --- a/arbitrator/jit/src/gostack.rs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -#![allow(clippy::useless_transmute)] - -use crate::{ - machine::{WasmEnv, WasmEnvMut}, - syscall::JsValue, -}; - -use ouroboros::self_referencing; -use rand_pcg::Pcg32; -use wasmer::{AsStoreRef, Memory, MemoryView, StoreRef, WasmPtr}; - -use std::collections::{BTreeSet, BinaryHeap}; - -#[self_referencing] -struct MemoryViewContainer { - memory: Memory, - #[borrows(memory)] - #[covariant] - view: MemoryView<'this>, -} - -impl MemoryViewContainer { - fn create(env: &WasmEnvMut<'_>) -> Self { - // this func exists to properly constrain the closure's type - fn closure<'a>( - store: &'a StoreRef, - ) -> impl (for<'b> FnOnce(&'b Memory) -> MemoryView<'b>) + 'a { - move |memory: &Memory| memory.view(&store) - } - - let store = env.as_store_ref(); - let memory = env.data().memory.clone().unwrap(); - let view_builder = closure(&store); - MemoryViewContainerBuilder { - memory, - view_builder, - } - .build() - } - - fn view(&self) -> &MemoryView { - self.borrow_view() - } -} - -pub struct GoStack { - start: u32, - memory: MemoryViewContainer, -} - -#[allow(dead_code)] -impl GoStack { - pub fn new<'a, 'b: 'a>(start: u32, env: &'a mut WasmEnvMut<'b>) -> (Self, &'a mut WasmEnv) { - let memory = MemoryViewContainer::create(env); - let sp = Self { start, memory }; - (sp, env.data_mut()) - } - - pub fn simple(start: u32, env: &WasmEnvMut<'_>) -> Self { - let memory = MemoryViewContainer::create(env); - Self { start, memory } - } - - pub fn shift_start(&mut self, offset: u32) { - self.start += offset; - } - - fn view(&self) -> &MemoryView { - self.memory.view() - } - - /// Returns the memory size, in bytes. - /// note: wasmer measures memory in 65536-byte pages. - pub fn memory_size(&self) -> u64 { - self.view().size().0 as u64 * 65536 - } - - pub fn relative_offset(&self, arg: u32) -> u32 { - (arg + 1) * 8 - } - - fn offset(&self, arg: u32) -> u32 { - self.start + self.relative_offset(arg) - } - - pub fn read_u8(&self, arg: u32) -> u8 { - self.read_u8_ptr(self.offset(arg)) - } - - pub fn read_u32(&self, arg: u32) -> u32 { - self.read_u32_ptr(self.offset(arg)) - } - - pub fn read_u64(&self, arg: u32) -> u64 { - self.read_u64_ptr(self.offset(arg)) - } - - pub fn read_u8_ptr(&self, ptr: u32) -> u8 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(self.view()).read().unwrap() - } - - pub fn read_u32_ptr(&self, ptr: u32) -> u32 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(self.view()).read().unwrap() - } - - pub fn read_u64_ptr(&self, ptr: u32) -> u64 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(self.view()).read().unwrap() - } - - pub fn write_u8(&self, arg: u32, x: u8) { - self.write_u8_ptr(self.offset(arg), x); - } - - pub fn write_u32(&self, arg: u32, x: u32) { - self.write_u32_ptr(self.offset(arg), x); - } - - pub fn write_u64(&self, arg: u32, x: u64) { - self.write_u64_ptr(self.offset(arg), x); - } - - pub fn write_u8_ptr(&self, ptr: u32, x: u8) { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(self.view()).write(x).unwrap(); - } - - pub fn write_u32_ptr(&self, ptr: u32, x: u32) { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(self.view()).write(x).unwrap(); - } - - pub fn write_u64_ptr(&self, ptr: u32, x: u64) { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(self.view()).write(x).unwrap(); - } - - pub fn read_slice(&self, ptr: u64, len: u64) -> Vec { - u32::try_from(ptr).expect("Go pointer not a u32"); // kept for consistency - let len = u32::try_from(len).expect("length isn't a u32") as usize; - let mut data = vec![0; len]; - self.view().read(ptr, &mut data).expect("failed to read"); - data - } - - pub fn write_slice(&self, ptr: u64, src: &[u8]) { - u32::try_from(ptr).expect("Go pointer not a u32"); - self.view().write(ptr, src).unwrap(); - } - - pub fn read_value_slice(&self, mut ptr: u64, len: u64) -> Vec { - let mut values = Vec::new(); - for _ in 0..len { - let p = u32::try_from(ptr).expect("Go pointer not a u32"); - values.push(JsValue::new(self.read_u64_ptr(p))); - ptr += 8; - } - values - } -} - -pub struct GoRuntimeState { - /// An increasing clock used when Go asks for time, measured in nanoseconds - pub time: u64, - /// The amount of time advanced each check. Currently 10 milliseconds - pub time_interval: u64, - /// The state of Go's timeouts - pub timeouts: TimeoutState, - /// Deterministic source of random data - pub rng: Pcg32, -} - -impl Default for GoRuntimeState { - fn default() -> Self { - Self { - time: 0, - time_interval: 10_000_000, - timeouts: TimeoutState::default(), - rng: Pcg32::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TimeoutInfo { - pub time: u64, - pub id: u32, -} - -impl Ord for TimeoutInfo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - other - .time - .cmp(&self.time) - .then_with(|| other.id.cmp(&self.id)) - } -} - -impl PartialOrd for TimeoutInfo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Default, Debug)] -pub struct TimeoutState { - /// Contains tuples of (time, id) - pub times: BinaryHeap, - pub pending_ids: BTreeSet, - pub next_id: u32, -} diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index c9119dd16..f51970c6d 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -1,29 +1,28 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::{ - arbcompress, gostack::GoRuntimeState, runtime, socket, syscall, syscall::JsRuntimeState, - wavmio, wavmio::Bytes32, Opts, + arbcompress, caller_env::GoRuntimeState, program, socket, stylus_backend::CothreadHandler, + wasip1_stub, wavmio, Opts, }; - -use arbutil::{Color, PreimageType}; -use eyre::{bail, Result, WrapErr}; +use arbutil::{Bytes32, Color, PreimageType}; +use eyre::{bail, ErrReport, Result, WrapErr}; use sha3::{Digest, Keccak256}; -use thiserror::Error; -use wasmer::{ - imports, CompilerConfig, Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, - RuntimeError, Store, TypedFunction, -}; -use wasmer_compiler_cranelift::Cranelift; - use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, fs::File, io::{self, Write}, io::{BufReader, BufWriter, ErrorKind, Read}, net::TcpStream, - time::Instant, + sync::Arc, + time::{Duration, Instant}, }; +use thiserror::Error; +use wasmer::{ + imports, CompilerConfig, Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, + Pages, RuntimeError, Store, +}; +use wasmer_compiler_cranelift::Cranelift; pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Store) { let file = &opts.binary; @@ -60,64 +59,77 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto }; let func_env = FunctionEnv::new(&mut store, env); - macro_rules! native { - ($func:expr) => { - Function::new_typed(&mut store, $func) - }; - } macro_rules! func { ($func:expr) => { Function::new_typed_with_env(&mut store, &func_env, $func) }; } - let imports = imports! { - "go" => { - "debug" => native!(runtime::go_debug), - - "runtime.resetMemoryDataView" => native!(runtime::reset_memory_data_view), - "runtime.wasmExit" => func!(runtime::wasm_exit), - "runtime.wasmWrite" => func!(runtime::wasm_write), - "runtime.nanotime1" => func!(runtime::nanotime1), - "runtime.walltime" => func!(runtime::walltime), - "runtime.walltime1" => func!(runtime::walltime1), - "runtime.scheduleTimeoutEvent" => func!(runtime::schedule_timeout_event), - "runtime.clearTimeoutEvent" => func!(runtime::clear_timeout_event), - "runtime.getRandomData" => func!(runtime::get_random_data), - - "syscall/js.finalizeRef" => func!(syscall::js_finalize_ref), - "syscall/js.stringVal" => func!(syscall::js_string_val), - "syscall/js.valueGet" => func!(syscall::js_value_get), - "syscall/js.valueSet" => func!(syscall::js_value_set), - "syscall/js.valueDelete" => func!(syscall::js_value_delete), - "syscall/js.valueIndex" => func!(syscall::js_value_index), - "syscall/js.valueSetIndex" => func!(syscall::js_value_set_index), - "syscall/js.valueCall" => func!(syscall::js_value_call), - "syscall/js.valueInvoke" => func!(syscall::js_value_invoke), - "syscall/js.valueNew" => func!(syscall::js_value_new), - "syscall/js.valueLength" => func!(syscall::js_value_length), - "syscall/js.valuePrepareString" => func!(syscall::js_value_prepare_string), - "syscall/js.valueLoadString" => func!(syscall::js_value_load_string), - "syscall/js.valueInstanceOf" => func!(syscall::js_value_instance_of), - "syscall/js.copyBytesToGo" => func!(syscall::js_copy_bytes_to_go), - "syscall/js.copyBytesToJS" => func!(syscall::js_copy_bytes_to_js), - - "github.com/offchainlabs/nitro/wavmio.getGlobalStateBytes32" => func!(wavmio::get_global_state_bytes32), - "github.com/offchainlabs/nitro/wavmio.setGlobalStateBytes32" => func!(wavmio::set_global_state_bytes32), - "github.com/offchainlabs/nitro/wavmio.getGlobalStateU64" => func!(wavmio::get_global_state_u64), - "github.com/offchainlabs/nitro/wavmio.setGlobalStateU64" => func!(wavmio::set_global_state_u64), - "github.com/offchainlabs/nitro/wavmio.readInboxMessage" => func!(wavmio::read_inbox_message), - "github.com/offchainlabs/nitro/wavmio.readDelayedInboxMessage" => func!(wavmio::read_delayed_inbox_message), - "github.com/offchainlabs/nitro/wavmio.resolvePreImage" => { + "arbcompress" => { + "brotli_compress" => func!(arbcompress::brotli_compress), + "brotli_decompress" => func!(arbcompress::brotli_decompress), + }, + "wavmio" => { + "getGlobalStateBytes32" => func!(wavmio::get_global_state_bytes32), + "setGlobalStateBytes32" => func!(wavmio::set_global_state_bytes32), + "getGlobalStateU64" => func!(wavmio::get_global_state_u64), + "setGlobalStateU64" => func!(wavmio::set_global_state_u64), + "readInboxMessage" => func!(wavmio::read_inbox_message), + "readDelayedInboxMessage" => func!(wavmio::read_delayed_inbox_message), + "resolvePreImage" => { #[allow(deprecated)] // we're just keeping this around until we no longer need to validate old replay binaries { func!(wavmio::resolve_keccak_preimage) } }, - "github.com/offchainlabs/nitro/wavmio.resolveTypedPreimage" => func!(wavmio::resolve_typed_preimage), - - "github.com/offchainlabs/nitro/arbcompress.brotliCompress" => func!(arbcompress::brotli_compress), - "github.com/offchainlabs/nitro/arbcompress.brotliDecompress" => func!(arbcompress::brotli_decompress), + "resolveTypedPreimage" => func!(wavmio::resolve_typed_preimage), + }, + "wasi_snapshot_preview1" => { + "proc_exit" => func!(wasip1_stub::proc_exit), + "environ_sizes_get" => func!(wasip1_stub::environ_sizes_get), + "fd_write" => func!(wasip1_stub::fd_write), + "environ_get" => func!(wasip1_stub::environ_get), + "fd_close" => func!(wasip1_stub::fd_close), + "fd_read" => func!(wasip1_stub::fd_read), + "fd_readdir" => func!(wasip1_stub::fd_readdir), + "fd_sync" => func!(wasip1_stub::fd_sync), + "fd_seek" => func!(wasip1_stub::fd_seek), + "fd_datasync" => func!(wasip1_stub::fd_datasync), + "path_open" => func!(wasip1_stub::path_open), + "path_create_directory" => func!(wasip1_stub::path_create_directory), + "path_remove_directory" => func!(wasip1_stub::path_remove_directory), + "path_readlink" => func!(wasip1_stub::path_readlink), + "path_rename" => func!(wasip1_stub::path_rename), + "path_filestat_get" => func!(wasip1_stub::path_filestat_get), + "path_unlink_file" => func!(wasip1_stub::path_unlink_file), + "fd_prestat_get" => func!(wasip1_stub::fd_prestat_get), + "fd_prestat_dir_name" => func!(wasip1_stub::fd_prestat_dir_name), + "fd_filestat_get" => func!(wasip1_stub::fd_filestat_get), + "fd_filestat_set_size" => func!(wasip1_stub::fd_filestat_set_size), + "fd_pread" => func!(wasip1_stub::fd_pread), + "fd_pwrite" => func!(wasip1_stub::fd_pwrite), + "sock_accept" => func!(wasip1_stub::sock_accept), + "sock_shutdown" => func!(wasip1_stub::sock_shutdown), + "sched_yield" => func!(wasip1_stub::sched_yield), + "clock_time_get" => func!(wasip1_stub::clock_time_get), + "random_get" => func!(wasip1_stub::random_get), + "args_sizes_get" => func!(wasip1_stub::args_sizes_get), + "args_get" => func!(wasip1_stub::args_get), + "poll_oneoff" => func!(wasip1_stub::poll_oneoff), + "fd_fdstat_get" => func!(wasip1_stub::fd_fdstat_get), + "fd_fdstat_set_flags" => func!(wasip1_stub::fd_fdstat_set_flags), + }, + "programs" => { + "new_program" => func!(program::new_program), + "pop" => func!(program::pop), + "set_response" => func!(program::set_response), + "get_request" => func!(program::get_request), + "get_request_data" => func!(program::get_request_data), + "start_program" => func!(program::start_program), + "send_response" => func!(program::send_response), + "create_stylus_config" => func!(program::create_stylus_config), + "create_evm_data" => func!(program::create_evm_data), + "activate" => func!(program::activate), }, }; @@ -125,23 +137,13 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto Ok(instance) => instance, Err(err) => panic!("Failed to create instance: {}", err.red()), }; - let memory = match instance.exports.get_memory("mem") { + let memory = match instance.exports.get_memory("memory") { Ok(memory) => memory.clone(), Err(err) => panic!("Failed to get memory: {}", err.red()), }; - let resume = match instance.exports.get_typed_function(&store, "resume") { - Ok(resume) => resume, - Err(err) => panic!("Failed to get the {} func: {}", "resume".red(), err.red()), - }; - let getsp = match instance.exports.get_typed_function(&store, "getsp") { - Ok(getsp) => getsp, - Err(err) => panic!("Failed to get the {} func: {}", "getsp".red(), err.red()), - }; let env = func_env.as_mut(&mut store); env.memory = Some(memory); - env.exports.resume = Some(resume); - env.exports.get_stack_pointer = Some(getsp); (instance, func_env, store) } @@ -153,6 +155,8 @@ pub enum Escape { Failure(String), #[error("hostio failed with `{0}`")] HostIO(String), + #[error("comms with child instance failed with `{0}`")] + Child(ErrReport), #[error("hostio socket failed with `{0}`")] SocketError(#[from] io::Error), } @@ -164,13 +168,9 @@ impl Escape { Err(Self::Exit(code)) } - pub fn hostio>(message: S) -> MaybeEscape { + pub fn hostio>(message: S) -> Result { Err(Self::HostIO(message.as_ref().to_string())) } - - pub fn failure>(message: S) -> MaybeEscape { - Err(Self::Failure(message.as_ref().to_string())) - } } impl From for Escape { @@ -184,7 +184,8 @@ impl From for Escape { pub type WasmEnvMut<'a> = FunctionEnvMut<'a, WasmEnv>; pub type Inbox = BTreeMap>; -pub type Preimages = BTreeMap>>; +pub type Preimages = BTreeMap>>; +pub type ModuleAsm = Arc<[u8]>; #[derive(Default)] pub struct WasmEnv { @@ -192,22 +193,22 @@ pub struct WasmEnv { pub memory: Option, /// Go's general runtime state pub go_state: GoRuntimeState, - /// The state of Go's js runtime - pub js_state: JsRuntimeState, /// An ordered list of the 8-byte globals pub small_globals: [u64; 2], /// An ordered list of the 32-byte globals pub large_globals: [Bytes32; 2], /// An oracle allowing the prover to reverse keccak256 pub preimages: Preimages, + /// A collection of programs called during the course of execution + pub module_asms: HashMap, /// The sequencer inbox's messages pub sequencer_messages: Inbox, /// The delayed inbox's messages pub delayed_messages: Inbox, /// The purpose and connections of this process pub process: ProcessEnv, - /// The exported funcs callable in hostio - pub exports: WasmEnvFuncs, + // threads + pub threads: Vec, } impl WasmEnv { @@ -264,10 +265,10 @@ impl WasmEnv { if arg.starts_with("0x") { arg = &arg[2..]; } - let mut bytes32 = Bytes32::default(); + let mut bytes32 = [0u8; 32]; hex::decode_to_slice(arg, &mut bytes32) .wrap_err_with(|| format!("failed to parse {} contents", name))?; - Ok(bytes32) + Ok(bytes32.into()) } None => Ok(Bytes32::default()), } @@ -280,7 +281,7 @@ impl WasmEnv { Ok(env) } - pub fn send_results(&mut self, error: Option, memory_used: u64) { + pub fn send_results(&mut self, error: Option, memory_used: Pages) { let writer = match &mut self.process.socket { Some((writer, _)) => writer, None => return, @@ -307,7 +308,7 @@ impl WasmEnv { check!(socket::write_u64(writer, self.small_globals[1])); check!(socket::write_bytes32(writer, &self.large_globals[0])); check!(socket::write_bytes32(writer, &self.large_globals[1])); - check!(socket::write_u64(writer, memory_used)); + check!(socket::write_u64(writer, memory_used.bytes().0 as u64)); check!(writer.flush()); } } @@ -321,6 +322,8 @@ pub struct ProcessEnv { pub socket: Option<(BufWriter, BufReader)>, /// A timestamp that helps with printing at various moments pub timestamp: Instant, + /// How long to wait on any child threads to compute a result + pub child_timeout: Duration, /// Whether the machine has reached the first wavmio instruction pub reached_wavmio: bool, } @@ -332,15 +335,8 @@ impl Default for ProcessEnv { debug: false, socket: None, timestamp: Instant::now(), + child_timeout: Duration::from_secs(15), reached_wavmio: false, } } } - -#[derive(Default)] -pub struct WasmEnvFuncs { - /// Calls `resume` from the go runtime - pub resume: Option>, - /// Calls `getsp` from the go runtime - pub get_stack_pointer: Option>, -} diff --git a/arbitrator/jit/src/main.rs b/arbitrator/jit/src/main.rs index 968da2a97..e432dc215 100644 --- a/arbitrator/jit/src/main.rs +++ b/arbitrator/jit/src/main.rs @@ -1,21 +1,20 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::machine::{Escape, WasmEnv}; - use arbutil::{color, Color}; -use structopt::StructOpt; -use wasmer::Value; - +use eyre::Result; use std::path::PathBuf; +use structopt::StructOpt; mod arbcompress; -mod gostack; +mod caller_env; mod machine; -mod runtime; +mod program; mod socket; -mod syscall; +mod stylus_backend; mod test; +mod wasip1_stub; mod wavmio; #[derive(StructOpt)] @@ -45,37 +44,24 @@ pub struct Opts { forks: bool, #[structopt(long)] debug: bool, + #[structopt(long)] + require_success: bool, } -fn main() { +fn main() -> Result<()> { let opts = Opts::from_args(); - let env = match WasmEnv::cli(&opts) { Ok(env) => env, - Err(err) => panic!("{}", err), + Err(err) => panic!("{err}"), }; let (instance, env, mut store) = machine::create(&opts, env); - let memory = instance.exports.get_memory("mem").unwrap(); - let memory = memory.view(&store); - - // To pass in the program name argument, we need to put it in memory. - // The Go linker guarantees a section of memory starting at byte 4096 is available for this purpose. - // https://github.com/golang/go/blob/252324e879e32f948d885f787decf8af06f82be9/misc/wasm/wasm_exec.js#L520 - let free_memory_base: i32 = 4096; - let name = free_memory_base; - let argv = name + 8; - - memory.write(name as u64, b"js\0").unwrap(); // write "js\0" to the name ptr - memory.write(argv as u64, &name.to_le_bytes()).unwrap(); // write the name ptr to the argv ptr - let run_args = &[Value::I32(1), Value::I32(argv)]; // pass argv with our single name arg - - let main = instance.exports.get_function("run").unwrap(); - let outcome = main.call(&mut store, run_args); + let main = instance.exports.get_function("_start").unwrap(); + let outcome = main.call(&mut store, &[]); let escape = match outcome { Ok(outcome) => { - println!("Go returned values {:?}", outcome); + println!("Go returned values {outcome:?}"); None } Err(outcome) => { @@ -92,6 +78,13 @@ fn main() { } }; + let memory_used = instance + .exports + .get_memory("memory") + .unwrap() + .view(&store) + .size(); + let env = env.as_mut(&mut store); let user = env.process.socket.is_none(); let time = format!("{}ms", env.process.timestamp.elapsed().as_millis()); @@ -102,11 +95,12 @@ fn main() { Some(Escape::Exit(x)) => (false, format!("Failed in {time} with exit code {x}.")), Some(Escape::Failure(err)) => (false, format!("Jit failed with {err} in {time}.")), Some(Escape::HostIO(err)) => (false, format!("Hostio failed with {err} in {time}.")), + Some(Escape::Child(err)) => (false, format!("Child failed with {err} in {time}.")), Some(Escape::SocketError(err)) => (false, format!("Socket failed with {err} in {time}.")), None => (false, "Machine exited prematurely".to_owned()), }; - if opts.debug { + if opts.debug || !success { println!("{message}"); } @@ -114,9 +108,13 @@ fn main() { true => None, false => Some(message), }; - let memory_used = memory.size().0 as u64 * 65_536; env.send_results(error, memory_used); + + if !success && opts.require_success { + std::process::exit(1); + } + Ok(()) } // require a usize be at least 32 bits wide diff --git a/arbitrator/jit/src/program.rs b/arbitrator/jit/src/program.rs new file mode 100644 index 000000000..c608a3cf8 --- /dev/null +++ b/arbitrator/jit/src/program.rs @@ -0,0 +1,265 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::caller_env::JitEnv; +use crate::machine::{Escape, MaybeEscape, WasmEnvMut}; +use crate::stylus_backend::exec_wasm; +use arbutil::Bytes32; +use arbutil::{evm::EvmData, format::DebugBytes, heapify}; +use caller_env::{GuestPtr, MemAccess}; +use eyre::eyre; +use prover::programs::prelude::StylusConfig; +use prover::{ + machine::Module, + programs::{config::PricingParams, prelude::*}, +}; + +/// activates a user program +pub fn activate( + mut env: WasmEnvMut, + wasm_ptr: GuestPtr, + wasm_size: u32, + pages_ptr: GuestPtr, + asm_estimate_ptr: GuestPtr, + init_cost_ptr: GuestPtr, + cached_init_cost_ptr: GuestPtr, + version: u16, + debug: u32, + codehash: GuestPtr, + module_hash_ptr: GuestPtr, + gas_ptr: GuestPtr, + err_buf: GuestPtr, + err_buf_len: u32, +) -> Result { + let (mut mem, _) = env.jit_env(); + let wasm = mem.read_slice(wasm_ptr, wasm_size as usize); + let codehash = &mem.read_bytes32(codehash); + let debug = debug != 0; + + let page_limit = mem.read_u16(pages_ptr); + let gas_left = &mut mem.read_u64(gas_ptr); + match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) { + Ok((module, data)) => { + mem.write_u64(gas_ptr, *gas_left); + mem.write_u16(pages_ptr, data.footprint); + mem.write_u32(asm_estimate_ptr, data.asm_estimate); + mem.write_u16(init_cost_ptr, data.init_cost); + mem.write_u16(cached_init_cost_ptr, data.cached_init_cost); + mem.write_bytes32(module_hash_ptr, module.hash()); + Ok(0) + } + Err(error) => { + let mut err_bytes = error.wrap_err("failed to activate").debug_bytes(); + err_bytes.truncate(err_buf_len as usize); + mem.write_slice(err_buf, &err_bytes); + mem.write_u64(gas_ptr, 0); + mem.write_u16(pages_ptr, 0); + mem.write_u32(asm_estimate_ptr, 0); + mem.write_u16(init_cost_ptr, 0); + mem.write_u16(cached_init_cost_ptr, 0); + mem.write_bytes32(module_hash_ptr, Bytes32::default()); + Ok(err_bytes.len() as u32) + } + } +} + +/// Links and creates user program (in jit starts it as well) +/// consumes both evm_data_handler and config_handler +/// returns module number +pub fn new_program( + mut env: WasmEnvMut, + compiled_hash_ptr: GuestPtr, + calldata_ptr: GuestPtr, + calldata_size: u32, + stylus_config_handler: u64, + evm_data_handler: u64, + gas: u64, +) -> Result { + let (mut mem, exec) = env.jit_env(); + let compiled_hash = mem.read_bytes32(compiled_hash_ptr); + let calldata = mem.read_slice(calldata_ptr, calldata_size as usize); + let evm_data: EvmData = unsafe { *Box::from_raw(evm_data_handler as *mut EvmData) }; + let config: JitConfig = unsafe { *Box::from_raw(stylus_config_handler as *mut JitConfig) }; + + // buy ink + let pricing = config.stylus.pricing; + let ink = pricing.gas_to_ink(gas); + + let Some(module) = exec.module_asms.get(&compiled_hash).cloned() else { + return Err(Escape::Failure(format!( + "module hash {:?} not found in {:?}", + compiled_hash, + exec.module_asms.keys() + ))); + }; + + let cothread = exec_wasm( + module, + calldata, + config.compile, + config.stylus, + evm_data, + ink, + ) + .unwrap(); + + exec.threads.push(cothread); + + Ok(exec.threads.len() as u32) +} + +/// starts the program (in jit waits for first request) +/// module MUST match last module number returned from new_program +/// returns request_id for the first request from the program +pub fn start_program(mut env: WasmEnvMut, module: u32) -> Result { + let (_, exec) = env.jit_env(); + + if exec.threads.len() as u32 != module || module == 0 { + return Escape::hostio(format!( + "got request for thread {module} but len is {}", + exec.threads.len() + )); + } + let thread = exec.threads.last_mut().unwrap(); + thread.wait_next_message()?; + let msg = thread.last_message()?; + Ok(msg.1) +} + +/// gets information about request according to id +/// request_id MUST be last request id returned from start_program or send_response +pub fn get_request(mut env: WasmEnvMut, id: u32, len_ptr: GuestPtr) -> Result { + let (mut mem, exec) = env.jit_env(); + let thread = exec.threads.last_mut().unwrap(); + let msg = thread.last_message()?; + if msg.1 != id { + return Escape::hostio("get_request id doesn't match"); + }; + mem.write_u32(len_ptr, msg.0.req_data.len() as u32); + Ok(msg.0.req_type) +} + +// gets data associated with last request. +// request_id MUST be last request receieved +// data_ptr MUST point to a buffer of at least the length returned by get_request +pub fn get_request_data(mut env: WasmEnvMut, id: u32, data_ptr: GuestPtr) -> MaybeEscape { + let (mut mem, exec) = env.jit_env(); + let thread = exec.threads.last_mut().unwrap(); + let msg = thread.last_message()?; + if msg.1 != id { + return Escape::hostio("get_request id doesn't match"); + }; + mem.write_slice(data_ptr, &msg.0.req_data); + Ok(()) +} + +/// sets response for the next request made +/// id MUST be the id of last request made +pub fn set_response( + mut env: WasmEnvMut, + id: u32, + gas: u64, + result_ptr: GuestPtr, + result_len: u32, + raw_data_ptr: GuestPtr, + raw_data_len: u32, +) -> MaybeEscape { + let (mem, exec) = env.jit_env(); + let result = mem.read_slice(result_ptr, result_len as usize); + let raw_data = mem.read_slice(raw_data_ptr, raw_data_len as usize); + + let thread = exec.threads.last_mut().unwrap(); + thread.set_response(id, result, raw_data, gas) +} + +/// sends previos response +/// MUST be called right after set_response to the same id +/// returns request_id for the next request +pub fn send_response(mut env: WasmEnvMut, req_id: u32) -> Result { + let (_, exec) = env.jit_env(); + let thread = exec.threads.last_mut().unwrap(); + let msg = thread.last_message()?; + if msg.1 != req_id { + return Escape::hostio("get_request id doesn't match"); + }; + thread.wait_next_message()?; + let msg = thread.last_message()?; + Ok(msg.1) +} + +/// removes the last created program +pub fn pop(mut env: WasmEnvMut) -> MaybeEscape { + let (_, exec) = env.jit_env(); + + match exec.threads.pop() { + None => Err(Escape::Child(eyre!("no child"))), + Some(mut thread) => thread.wait_done(), + } +} + +pub struct JitConfig { + stylus: StylusConfig, + compile: CompileConfig, +} + +/// Creates a `StylusConfig` from its component parts. +pub fn create_stylus_config( + mut _env: WasmEnvMut, + version: u16, + max_depth: u32, + ink_price: u32, + debug: u32, +) -> Result { + let stylus = StylusConfig { + version, + max_depth, + pricing: PricingParams { ink_price }, + }; + let compile = CompileConfig::version(version, debug != 0); + let res = heapify(JitConfig { stylus, compile }); + Ok(res as u64) +} + +/// Creates an `EvmData` handler from its component parts. +pub fn create_evm_data( + mut env: WasmEnvMut, + block_basefee_ptr: GuestPtr, + chainid: u64, + block_coinbase_ptr: GuestPtr, + block_gas_limit: u64, + block_number: u64, + block_timestamp: u64, + contract_address_ptr: GuestPtr, + module_hash_ptr: GuestPtr, + msg_sender_ptr: GuestPtr, + msg_value_ptr: GuestPtr, + tx_gas_price_ptr: GuestPtr, + tx_origin_ptr: GuestPtr, + cached: u32, + reentrant: u32, +) -> Result { + let (mut mem, _) = env.jit_env(); + + let evm_data = EvmData { + block_basefee: mem.read_bytes32(block_basefee_ptr), + cached: cached != 0, + chainid, + block_coinbase: mem.read_bytes20(block_coinbase_ptr), + block_gas_limit, + block_number, + block_timestamp, + contract_address: mem.read_bytes20(contract_address_ptr), + module_hash: mem.read_bytes32(module_hash_ptr), + msg_sender: mem.read_bytes20(msg_sender_ptr), + msg_value: mem.read_bytes32(msg_value_ptr), + tx_gas_price: mem.read_bytes32(tx_gas_price_ptr), + tx_origin: mem.read_bytes20(tx_origin_ptr), + reentrant, + return_data_len: 0, + tracing: false, + }; + let res = heapify(evm_data); + Ok(res as u64) +} diff --git a/arbitrator/jit/src/runtime.rs b/arbitrator/jit/src/runtime.rs deleted file mode 100644 index d547a0655..000000000 --- a/arbitrator/jit/src/runtime.rs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -use crate::{ - gostack::{GoStack, TimeoutInfo}, - machine::{Escape, MaybeEscape, WasmEnvMut}, -}; - -use rand::RngCore; - -use std::io::Write; - -pub fn go_debug(x: u32) { - println!("go debug: {x}") -} - -pub fn reset_memory_data_view(_: u32) {} - -pub fn wasm_exit(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, _) = GoStack::new(sp, &mut env); - Escape::exit(sp.read_u32(0)) -} - -pub fn wasm_write(mut env: WasmEnvMut, sp: u32) { - let (sp, _) = GoStack::new(sp, &mut env); - let fd = sp.read_u64(0); - let ptr = sp.read_u64(1); - let len = sp.read_u32(2); - let buf = sp.read_slice(ptr, len.into()); - if fd == 2 { - let stderr = std::io::stderr(); - let mut stderr = stderr.lock(); - stderr.write_all(&buf).unwrap(); - } else { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - stdout.write_all(&buf).unwrap(); - } -} - -pub fn nanotime1(mut env: WasmEnvMut, sp: u32) { - let (sp, env) = GoStack::new(sp, &mut env); - env.go_state.time += env.go_state.time_interval; - sp.write_u64(0, env.go_state.time); -} - -pub fn walltime(mut env: WasmEnvMut, sp: u32) { - let (sp, env) = GoStack::new(sp, &mut env); - env.go_state.time += env.go_state.time_interval; - sp.write_u64(0, env.go_state.time / 1_000_000_000); - sp.write_u32(1, (env.go_state.time % 1_000_000_000) as u32); -} - -pub fn walltime1(mut env: WasmEnvMut, sp: u32) { - let (sp, env) = GoStack::new(sp, &mut env); - env.go_state.time += env.go_state.time_interval; - sp.write_u64(0, env.go_state.time / 1_000_000_000); - sp.write_u64(1, env.go_state.time % 1_000_000_000); -} - -pub fn schedule_timeout_event(mut env: WasmEnvMut, sp: u32) { - let (sp, env) = GoStack::new(sp, &mut env); - let mut time = sp.read_u64(0); - time = time.saturating_mul(1_000_000); // milliseconds to nanoseconds - time = time.saturating_add(env.go_state.time); // add the current time to the delay - - let timeouts = &mut env.go_state.timeouts; - let id = timeouts.next_id; - timeouts.next_id += 1; - timeouts.times.push(TimeoutInfo { time, id }); - timeouts.pending_ids.insert(id); - - sp.write_u32(1, id); -} - -pub fn clear_timeout_event(mut env: WasmEnvMut, sp: u32) { - let (sp, env) = GoStack::new(sp, &mut env); - - let id = sp.read_u32(0); - if !env.go_state.timeouts.pending_ids.remove(&id) { - eprintln!("Go attempting to clear not pending timeout event {id}"); - } -} - -pub fn get_random_data(mut env: WasmEnvMut, sp: u32) { - let (sp, env) = GoStack::new(sp, &mut env); - - let mut ptr = u32::try_from(sp.read_u64(0)).expect("Go getRandomData pointer not a u32"); - let mut len = sp.read_u64(1); - while len >= 4 { - let next = env.go_state.rng.next_u32(); - sp.write_u32_ptr(ptr, next); - ptr += 4; - len -= 4; - } - if len > 0 { - let mut rem = env.go_state.rng.next_u32(); - for _ in 0..len { - sp.write_u8_ptr(ptr, rem as u8); - ptr += 1; - rem >>= 8; - } - } -} diff --git a/arbitrator/jit/src/socket.rs b/arbitrator/jit/src/socket.rs index 3941763a0..004b8eb44 100644 --- a/arbitrator/jit/src/socket.rs +++ b/arbitrator/jit/src/socket.rs @@ -1,16 +1,16 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +use arbutil::Bytes32; use std::{ io, io::{BufReader, BufWriter, Read, Write}, net::TcpStream, }; -use crate::wavmio::Bytes32; - pub const SUCCESS: u8 = 0x0; pub const FAILURE: u8 = 0x1; +// pub const PREIMAGE: u8 = 0x2; // not used pub const ANOTHER: u8 = 0x3; pub const READY: u8 = 0x4; @@ -19,14 +19,19 @@ pub fn read_u8(reader: &mut BufReader) -> Result { reader.read_exact(&mut buf).map(|_| u8::from_be_bytes(buf)) } +pub fn read_u32(reader: &mut BufReader) -> Result { + let mut buf = [0; 4]; + reader.read_exact(&mut buf).map(|_| u32::from_be_bytes(buf)) +} + pub fn read_u64(reader: &mut BufReader) -> Result { let mut buf = [0; 8]; reader.read_exact(&mut buf).map(|_| u64::from_be_bytes(buf)) } pub fn read_bytes32(reader: &mut BufReader) -> Result { - let mut buf = Bytes32::default(); - reader.read_exact(&mut buf).map(|_| buf) + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf).map(|_| buf.into()) } pub fn read_bytes(reader: &mut BufReader) -> Result, io::Error> { @@ -36,6 +41,10 @@ pub fn read_bytes(reader: &mut BufReader) -> Result, io::Err Ok(buf) } +pub fn read_boxed_slice(reader: &mut BufReader) -> Result, io::Error> { + Ok(Vec::into_boxed_slice(read_bytes(reader)?)) +} + pub fn write_u8(writer: &mut BufWriter, data: u8) -> Result<(), io::Error> { let buf = [data; 1]; writer.write_all(&buf) @@ -47,7 +56,7 @@ pub fn write_u64(writer: &mut BufWriter, data: u64) -> Result<(), io: } pub fn write_bytes32(writer: &mut BufWriter, data: &Bytes32) -> Result<(), io::Error> { - writer.write_all(data) + writer.write_all(data.as_slice()) } pub fn write_bytes(writer: &mut BufWriter, data: &[u8]) -> Result<(), io::Error> { diff --git a/arbitrator/jit/src/stylus_backend.rs b/arbitrator/jit/src/stylus_backend.rs new file mode 100644 index 000000000..61dbf258d --- /dev/null +++ b/arbitrator/jit/src/stylus_backend.rs @@ -0,0 +1,188 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::machine::{Escape, MaybeEscape}; +use arbutil::evm::api::VecReader; +use arbutil::evm::{ + api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET}, + req::EvmApiRequestor, + req::RequestHandler, + user::UserOutcome, + EvmData, +}; +use eyre::{eyre, Result}; +use prover::programs::prelude::*; +use std::thread; +use std::time::Duration; +use std::{ + sync::{ + mpsc::{self, Receiver, SyncSender}, + Arc, + }, + thread::JoinHandle, +}; +use stylus::{native::NativeInstance, run::RunProgram}; + +struct MessageToCothread { + result: Vec, + raw_data: Vec, + cost: u64, +} + +#[derive(Clone)] +pub struct MessageFromCothread { + pub req_type: u32, + pub req_data: Vec, +} + +struct CothreadRequestor { + tx: SyncSender, + rx: Receiver, +} + +impl RequestHandler for CothreadRequestor { + fn request( + &mut self, + req_type: EvmApiMethod, + req_data: impl AsRef<[u8]>, + ) -> (Vec, VecReader, u64) { + let msg = MessageFromCothread { + req_type: req_type as u32 + EVM_API_METHOD_REQ_OFFSET, + req_data: req_data.as_ref().to_vec(), + }; + + if let Err(error) = self.tx.send(msg) { + panic!("failed sending request from cothread: {error}"); + } + match self.rx.recv_timeout(Duration::from_secs(5)) { + Ok(response) => ( + response.result, + VecReader::new(response.raw_data), + response.cost, + ), + Err(_) => panic!("no response from main thread"), + } + } +} + +pub struct CothreadHandler { + tx: SyncSender, + rx: Receiver, + thread: Option>, + last_request: Option<(MessageFromCothread, u32)>, +} + +impl CothreadHandler { + pub fn wait_next_message(&mut self) -> MaybeEscape { + let msg = self.rx.recv_timeout(Duration::from_secs(10)); + let Ok(msg) = msg else { + return Escape::hostio("did not receive message"); + }; + self.last_request = Some((msg, 0x33333333)); // TODO: Ids + Ok(()) + } + + pub fn wait_done(&mut self) -> MaybeEscape { + let error = || Escape::Child(eyre!("no child")); + let status = self.thread.take().ok_or_else(error)?.join(); + match status { + Ok(res) => res, + Err(_) => Escape::hostio("failed joining child process"), + } + } + + pub fn last_message(&self) -> Result<(MessageFromCothread, u32), Escape> { + self.last_request + .clone() + .ok_or_else(|| Escape::HostIO("no message waiting".to_string())) + } + + pub fn set_response( + &mut self, + id: u32, + result: Vec, + raw_data: Vec, + cost: u64, + ) -> MaybeEscape { + let Some(msg) = self.last_request.clone() else { + return Escape::hostio("trying to set response but no message pending"); + }; + if msg.1 != id { + return Escape::hostio("trying to set response for wrong message id"); + }; + let msg = MessageToCothread { + result, + raw_data, + cost, + }; + if let Err(err) = self.tx.send(msg) { + return Escape::hostio(format!("failed to send response to stylus thread: {err:?}")); + }; + Ok(()) + } +} + +/// Executes a wasm on a new thread +pub fn exec_wasm( + module: Arc<[u8]>, + calldata: Vec, + compile: CompileConfig, + config: StylusConfig, + evm_data: EvmData, + ink: u64, +) -> Result { + let (tothread_tx, tothread_rx) = mpsc::sync_channel::(0); + let (fromthread_tx, fromthread_rx) = mpsc::sync_channel::(0); + + let cothread = CothreadRequestor { + tx: fromthread_tx, + rx: tothread_rx, + }; + + let evm_api = EvmApiRequestor::new(cothread); + + let mut instance = + unsafe { NativeInstance::deserialize(&module, compile.clone(), evm_api, evm_data) }?; + + let thread = thread::spawn(move || { + let outcome = instance.run_main(&calldata, config, ink); + + let ink_left = match outcome.as_ref() { + Ok(UserOutcome::OutOfStack) => 0, // take all ink when out of stack + _ => instance.ink_left().into(), + }; + + let outcome = match outcome { + Err(e) | Ok(UserOutcome::Failure(e)) => UserOutcome::Failure(e.wrap_err("call failed")), + Ok(outcome) => outcome, + }; + + let (out_kind, data) = outcome.into_data(); + let gas_left = config.pricing.ink_to_gas(ink_left); + + let mut output = Vec::with_capacity(8 + data.len()); + output.extend(gas_left.to_be_bytes()); + output.extend(data); + + let msg = MessageFromCothread { + req_data: output, + req_type: out_kind as u32, + }; + instance + .env_mut() + .evm_api + .request_handler() + .tx + .send(msg) + .or_else(|_| Escape::hostio("failed sending messaage to thread")) + }); + + Ok(CothreadHandler { + tx: tothread_tx, + rx: fromthread_rx, + thread: Some(thread), + last_request: None, + }) +} diff --git a/arbitrator/jit/src/wasip1_stub.rs b/arbitrator/jit/src/wasip1_stub.rs new file mode 100644 index 000000000..0b525e6a9 --- /dev/null +++ b/arbitrator/jit/src/wasip1_stub.rs @@ -0,0 +1,162 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::caller_env::{JitEnv, JitExecEnv}; +use crate::machine::{Escape, WasmEnvMut}; +use caller_env::{self, wasip1_stub::Errno, GuestPtr}; + +pub fn proc_exit(mut _env: WasmEnvMut, code: u32) -> Result<(), Escape> { + Err(Escape::Exit(code)) +} + +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + $( + pub fn $func_name(mut src: WasmEnvMut, $($arg_name : $arg_type),*) -> Result<$return_type, Escape> { + let (mut mem, wenv) = src.jit_env(); + + Ok(caller_env::wasip1_stub::$func_name(&mut mem, &mut JitExecEnv { wenv }, $($arg_name),*)) + } + )* + }; +} + +wrap! { + fn clock_time_get( + clock_id: u32, + precision: u64, + time_ptr: GuestPtr + ) -> Errno; + + fn random_get(buf: GuestPtr, len: u32) -> Errno; + + fn environ_get(a: GuestPtr, b: GuestPtr) -> Errno; + fn environ_sizes_get(length_ptr: GuestPtr, data_size_ptr: GuestPtr) -> Errno; + + fn fd_read(a: u32, b: u32, c: u32, d: u32) -> Errno; + fn fd_close(fd: u32) -> Errno; + fn fd_write( + fd: u32, + iovecs_ptr: GuestPtr, + iovecs_len: u32, + ret_ptr: GuestPtr + ) -> Errno; + + fn fd_readdir( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_sync(a: u32) -> Errno; + + fn fd_seek( + fd: u32, + offset: u64, + whence: u8, + filesize: u32 + ) -> Errno; + + fn fd_datasync(_fd: u32) -> Errno; + + fn path_open( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u64, + g: u64, + h: u32, + i: u32 + ) -> Errno; + + fn path_create_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_remove_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_readlink( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_rename( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_filestat_get( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32 + ) -> Errno; + + fn path_unlink_file(a: u32, b: u32, c: u32) -> Errno; + + fn fd_prestat_get(a: u32, b: u32) -> Errno; + fn fd_prestat_dir_name(a: u32, b: u32, c: u32) -> Errno; + + fn fd_filestat_get(fd: u32, _filestat: u32) -> Errno; + fn fd_filestat_set_size(fd: u32, size: u64) -> Errno; + + fn fd_pread( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_pwrite( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn sock_accept(_fd: u32, a: u32, b: u32) -> Errno; + fn sock_shutdown(a: u32, b: u32) -> Errno; + + fn sched_yield() -> Errno; + + fn args_sizes_get( + length_ptr: GuestPtr, + data_size_ptr: GuestPtr + ) -> Errno; + + fn args_get(argv_buf: GuestPtr, data_buf: GuestPtr) -> Errno; + + fn fd_fdstat_get(a: u32, b: u32) -> Errno; + fn fd_fdstat_set_flags(a: u32, b: u32) -> Errno; + + // we always simulate a timeout + fn poll_oneoff( + in_subs: GuestPtr, + out_evt: GuestPtr, + nsubscriptions: u32, + nevents_ptr: GuestPtr + ) -> Errno +} diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs index dfc7f2177..7fa21dde1 100644 --- a/arbitrator/jit/src/wavmio.rs +++ b/arbitrator/jit/src/wavmio.rs @@ -1,13 +1,15 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::{ - gostack::GoStack, - machine::{Escape, Inbox, MaybeEscape, WasmEnv, WasmEnvMut}, + caller_env::JitEnv, + machine::{Escape, MaybeEscape, WasmEnv, WasmEnvMut}, socket, }; - use arbutil::{Color, PreimageType}; +use caller_env::{GuestPtr, MemAccess}; +use sha2::Sha256; +use sha3::{Digest, Keccak256}; use std::{ io, io::{BufReader, BufWriter, ErrorKind}, @@ -15,166 +17,139 @@ use std::{ time::Instant, }; -pub type Bytes32 = [u8; 32]; - -pub fn get_global_state_bytes32(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - ready_hostio(env)?; - - let global = sp.read_u64(0) as u32 as usize; - let out_ptr = sp.read_u64(1); - let mut out_len = sp.read_u64(2) as usize; - if out_len < 32 { - eprintln!("Go trying to read block hash into {out_len} bytes long buffer"); - } else { - out_len = 32; - } +/// Reads 32-bytes of global state. +pub fn get_global_state_bytes32(mut env: WasmEnvMut, idx: u32, out_ptr: GuestPtr) -> MaybeEscape { + let (mut mem, exec) = env.jit_env(); + ready_hostio(exec)?; - let global = match env.large_globals.get(global) { - Some(global) => global, - None => return Escape::hostio("global read out of bounds in wavmio.getGlobalStateBytes32"), + let Some(global) = exec.large_globals.get(idx as usize) else { + return Escape::hostio("global read out of bounds in wavmio.getGlobalStateBytes32"); }; - sp.write_slice(out_ptr, &global[..out_len]); + mem.write_slice(out_ptr, &global[..32]); Ok(()) } -pub fn set_global_state_bytes32(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - ready_hostio(env)?; +/// Writes 32-bytes of global state. +pub fn set_global_state_bytes32(mut env: WasmEnvMut, idx: u32, src_ptr: GuestPtr) -> MaybeEscape { + let (mem, exec) = env.jit_env(); + ready_hostio(exec)?; - let global = sp.read_u64(0) as u32 as usize; - let src_ptr = sp.read_u64(1); - let src_len = sp.read_u64(2); - if src_len != 32 { - eprintln!("Go trying to set 32-byte global with a {src_len} bytes long buffer"); - return Ok(()); - } - - let slice = sp.read_slice(src_ptr, src_len); + let slice = mem.read_slice(src_ptr, 32); let slice = &slice.try_into().unwrap(); - match env.large_globals.get_mut(global) { + match exec.large_globals.get_mut(idx as usize) { Some(global) => *global = *slice, - None => { - return Escape::hostio("global write out of bounds in wavmio.setGlobalStateBytes32") - } - } + None => return Escape::hostio("global write oob in wavmio.setGlobalStateBytes32"), + }; Ok(()) } -pub fn get_global_state_u64(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - ready_hostio(env)?; +/// Reads 8-bytes of global state +pub fn get_global_state_u64(mut env: WasmEnvMut, idx: u32) -> Result { + let (_, exec) = env.jit_env(); + ready_hostio(exec)?; - let global = sp.read_u64(0) as u32 as usize; - match env.small_globals.get(global) { - Some(global) => sp.write_u64(1, *global), - None => return Escape::hostio("global read out of bounds in wavmio.getGlobalStateU64"), + match exec.small_globals.get(idx as usize) { + Some(global) => Ok(*global), + None => Escape::hostio("global read out of bounds in wavmio.getGlobalStateU64"), } - Ok(()) } -pub fn set_global_state_u64(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - ready_hostio(env)?; +/// Writes 8-bytes of global state +pub fn set_global_state_u64(mut env: WasmEnvMut, idx: u32, val: u64) -> MaybeEscape { + let (_, exec) = env.jit_env(); + ready_hostio(exec)?; - let global = sp.read_u64(0) as u32 as usize; - match env.small_globals.get_mut(global) { - Some(global) => *global = sp.read_u64(1), + match exec.small_globals.get_mut(idx as usize) { + Some(global) => *global = val, None => return Escape::hostio("global write out of bounds in wavmio.setGlobalStateU64"), } Ok(()) } -pub fn read_inbox_message(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - ready_hostio(env)?; - - let inbox = &env.sequencer_messages; - inbox_message_impl(&sp, inbox, "wavmio.readInboxMessage") -} - -pub fn read_delayed_inbox_message(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - ready_hostio(env)?; - - let inbox = &env.delayed_messages; - inbox_message_impl(&sp, inbox, "wavmio.readDelayedInboxMessage") -} - -/// Reads an inbox message -/// note: the order of the checks is very important. -fn inbox_message_impl(sp: &GoStack, inbox: &Inbox, name: &str) -> MaybeEscape { - let msg_num = sp.read_u64(0); - let offset = sp.read_u64(1); - let out_ptr = sp.read_u64(2); - let out_len = sp.read_u64(3); - if out_len != 32 { - eprintln!("Go trying to read inbox message with out len {out_len} in {name}"); - sp.write_u64(5, 0); - return Ok(()); - } - - macro_rules! error { - ($text:expr $(,$args:expr)*) => {{ - let text = format!($text $(,$args)*); - return Escape::hostio(&text) - }}; - } - - let message = match inbox.get(&msg_num) { +/// Reads an inbox message. +pub fn read_inbox_message( + mut env: WasmEnvMut, + msg_num: u64, + offset: u32, + out_ptr: GuestPtr, +) -> Result { + let (mut mem, exec) = env.jit_env(); + ready_hostio(exec)?; + + let message = match exec.sequencer_messages.get(&msg_num) { Some(message) => message, - None => error!("missing inbox message {msg_num} in {name}"), + None => return Escape::hostio(format!("missing sequencer inbox message {msg_num}")), }; + let offset = offset as usize; + let len = std::cmp::min(32, message.len().saturating_sub(offset)); + let read = message.get(offset..(offset + len)).unwrap_or_default(); + mem.write_slice(out_ptr, read); + Ok(read.len() as u32) +} - if out_ptr + 32 > sp.memory_size() { - error!("unknown message type in {name}"); - } - let offset = match u32::try_from(offset) { - Ok(offset) => offset as usize, - Err(_) => error!("bad offset {offset} in {name}"), +/// Reads a delayed inbox message. +pub fn read_delayed_inbox_message( + mut env: WasmEnvMut, + msg_num: u64, + offset: u32, + out_ptr: GuestPtr, +) -> Result { + let (mut mem, exec) = env.jit_env(); + ready_hostio(exec)?; + + let message = match exec.delayed_messages.get(&msg_num) { + Some(message) => message, + None => return Escape::hostio(format!("missing delayed inbox message {msg_num}")), }; - + let offset = offset as usize; let len = std::cmp::min(32, message.len().saturating_sub(offset)); let read = message.get(offset..(offset + len)).unwrap_or_default(); - sp.write_slice(out_ptr, read); - sp.write_u64(5, read.len() as u64); - Ok(()) + mem.write_slice(out_ptr, read); + Ok(read.len() as u32) } +/// Retrieves the preimage of the given hash. #[deprecated] // we're just keeping this around until we no longer need to validate old replay binaries -pub fn resolve_keccak_preimage(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (sp, env) = GoStack::new(sp, &mut env); - resolve_preimage_impl(env, sp, 0, "wavmio.ResolvePreImage") +pub fn resolve_keccak_preimage( + env: WasmEnvMut, + hash_ptr: GuestPtr, + offset: u32, + out_ptr: GuestPtr, +) -> Result { + resolve_preimage_impl(env, 0, hash_ptr, offset, out_ptr, "wavmio.ResolvePreImage") } -pub fn resolve_typed_preimage(mut env: WasmEnvMut, sp: u32) -> MaybeEscape { - let (mut sp, env) = GoStack::new(sp, &mut env); - let preimage_type = sp.read_u8(0); - sp.shift_start(8); // to account for the preimage type being the first slot - resolve_preimage_impl(env, sp, preimage_type, "wavmio.ResolveTypedPreimage") +pub fn resolve_typed_preimage( + env: WasmEnvMut, + preimage_type: u8, + hash_ptr: GuestPtr, + offset: u32, + out_ptr: GuestPtr, +) -> Result { + resolve_preimage_impl( + env, + preimage_type, + hash_ptr, + offset, + out_ptr, + "wavmio.ResolveTypedPreimage", + ) } pub fn resolve_preimage_impl( - env: &mut WasmEnv, - sp: GoStack, + mut env: WasmEnvMut, preimage_type: u8, + hash_ptr: GuestPtr, + offset: u32, + out_ptr: GuestPtr, name: &str, -) -> MaybeEscape { - let hash_ptr = sp.read_u64(0); - let hash_len = sp.read_u64(1); - let offset = sp.read_u64(3); - let out_ptr = sp.read_u64(4); - let out_len = sp.read_u64(5); - if hash_len != 32 || out_len != 32 { - eprintln!("Go trying to resolve pre image with hash len {hash_len} and out len {out_len}"); - sp.write_u64(7, 0); - return Ok(()); - } +) -> Result { + let (mut mem, exec) = env.jit_env(); + let offset = offset as usize; let Ok(preimage_type) = preimage_type.try_into() else { eprintln!("Go trying to resolve pre image with unknown type {preimage_type}"); - sp.write_u64(7, 0); - return Ok(()); + return Ok(0); }; macro_rules! error { @@ -184,24 +159,41 @@ pub fn resolve_preimage_impl( }}; } - let hash = sp.read_slice(hash_ptr, hash_len); - let hash: &[u8; 32] = &hash.try_into().unwrap(); - let hash_hex = hex::encode(hash); + let hash = mem.read_bytes32(hash_ptr); - let Some(preimage) = env.preimages.get(&preimage_type).and_then(|m| m.get(hash)) else { - error!("Missing requested preimage for preimage type {preimage_type:?} hash {hash_hex} in {name}"); + let Some(preimage) = exec + .preimages + .get(&preimage_type) + .and_then(|m| m.get(&hash)) + else { + let hash_hex = hex::encode(hash); + error!("Missing requested preimage for hash {hash_hex} in {name}") }; - let offset = match u32::try_from(offset) { - Ok(offset) if offset % 32 == 0 => offset as usize, - _ => error!("bad offset {offset} in {name}"), + // Check if preimage rehashes to the provided hash. Exclude blob preimages + let calculated_hash: [u8; 32] = match preimage_type { + PreimageType::Keccak256 => Keccak256::digest(preimage).into(), + PreimageType::Sha2_256 => Sha256::digest(preimage).into(), + PreimageType::EthVersionedHash => *hash, + PreimageType::EigenDAHash => *hash, + }; + if calculated_hash != *hash { + error!( + "Calculated hash {} of preimage {} does not match provided hash {}", + hex::encode(calculated_hash), + hex::encode(preimage), + hex::encode(*hash) + ); + } + + if offset % 32 != 0 { + error!("bad offset {offset} in {name}") }; let len = std::cmp::min(32, preimage.len().saturating_sub(offset)); let read = preimage.get(offset..(offset + len)).unwrap_or_default(); - sp.write_slice(out_ptr, read); - sp.write_u64(7, read.len() as u64); - Ok(()) + mem.write_slice(out_ptr, read); + Ok(read.len() as u32) } fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { @@ -237,7 +229,7 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { address.pop(); // pop the newline if address.is_empty() { - return Ok(()); + return Escape::exit(0); } if debug { println!("Child will connect to {address}"); @@ -281,12 +273,12 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { env.delayed_messages.insert(position, message); } - let preimage_types = socket::read_u64(stream)?; + let preimage_types = socket::read_u32(stream)?; for _ in 0..preimage_types { let preimage_ty = PreimageType::try_from(socket::read_u8(stream)?) .map_err(|e| Escape::Failure(e.to_string()))?; let map = env.preimages.entry(preimage_ty).or_default(); - let preimage_count = socket::read_u64(stream)?; + let preimage_count = socket::read_u32(stream)?; for _ in 0..preimage_count { let hash = socket::read_bytes32(stream)?; let preimage = socket::read_bytes(stream)?; @@ -294,6 +286,13 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { } } + let programs_count = socket::read_u32(stream)?; + for _ in 0..programs_count { + let module_hash = socket::read_bytes32(stream)?; + let module_asm = socket::read_boxed_slice(stream)?; + env.module_asms.insert(module_hash, module_asm.into()); + } + if socket::read_u8(stream)? != socket::READY { return Escape::hostio("failed to parse global state"); } diff --git a/arbitrator/langs/bf b/arbitrator/langs/bf new file mode 160000 index 000000000..062b87bad --- /dev/null +++ b/arbitrator/langs/bf @@ -0,0 +1 @@ +Subproject commit 062b87bad1ec00d42b9cc2b5ee41e63cd6ff1cbb diff --git a/arbitrator/langs/c b/arbitrator/langs/c new file mode 160000 index 000000000..29fe05d68 --- /dev/null +++ b/arbitrator/langs/c @@ -0,0 +1 @@ +Subproject commit 29fe05d68672797572080084b0f5f0a282e298ef diff --git a/arbitrator/langs/rust b/arbitrator/langs/rust new file mode 160000 index 000000000..7bb07e556 --- /dev/null +++ b/arbitrator/langs/rust @@ -0,0 +1 @@ +Subproject commit 7bb07e556d2da4e623f13bfb099a99f9d85cc297 diff --git a/arbitrator/prover/Cargo.toml b/arbitrator/prover/Cargo.toml index 3d05565a8..9af28bc09 100644 --- a/arbitrator/prover/Cargo.toml +++ b/arbitrator/prover/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "prover" version = "0.1.0" -edition = "2018" +edition = "2021" publish = false [dependencies] bincode = "1.3.3" -brotli2 = "0.3.2" +derivative = "2.2.0" digest = "0.9.0" eyre = "0.6.5" fnv = "1.0.7" @@ -15,20 +15,28 @@ libc = "0.2.108" nom = "7.0.0" nom-leb128 = "0.2.0" num = "0.4" -rayon = "1.5.1" rustc-demangle = "0.1.21" serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = "1.0.67" sha3 = "0.9.1" static_assertions = "1.1.0" structopt = "0.3.23" -wasmparser = "0.84.0" -wat = "1.0.56" serde_with = "1.12.1" -lazy_static = "1.4.0" +parking_lot = "0.12.1" +lazy_static.workspace = true +itertools = "0.10.5" +wat = "1.0.56" smallvec = { version = "1.10.0", features = ["serde"] } +rayon = { version = "1.5.1", optional = true } arbutil = { path = "../arbutil/" } -c-kzg = "0.4.0" # TODO: look into switching to rust-kzg (no crates.io release or hosted rustdoc yet) +brotli = { path = "../brotli/" } +wasmer = { path = "../tools/wasmer/lib/api", optional = true } +wasmer-types = { path = "../tools/wasmer/lib/types" } +wasmer-compiler-singlepass = { path = "../tools/wasmer/lib/compiler-singlepass", optional = true, default-features = false, features = ["std", "unwind", "avx"] } +wasmparser.workspace = true +num-derive = "0.4.1" +num-traits = "0.2.17" +c-kzg = { version = "0.4.0", optional = true } # TODO: look into switching to rust-kzg (no crates.io release or hosted rustdoc yet) sha2 = "0.9.9" ark-bn254 = "0.4.0" ark-std = "0.4.0" @@ -38,7 +46,15 @@ ark-serialize = "0.4.0" num-bigint = "0.4" kzgbn254 = { path = "../rust-kzg-bn254", package = "rust-kzg-bn254" } +lru = "0.12.3" +once_cell = "1.19.0" [lib] name = "prover" -crate-type = ["staticlib","lib"] +crate-type = ["staticlib", "lib"] + +[features] +default = ["native", "rayon", "singlepass_rayon"] +native = ["dep:wasmer", "dep:wasmer-compiler-singlepass", "brotli/wasmer_traits", "dep:c-kzg"] +singlepass_rayon = ["wasmer-compiler-singlepass?/rayon"] +rayon = ["dep:rayon"] diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs index 3aa857449..f6c3e9fe8 100644 --- a/arbitrator/prover/src/binary.rs +++ b/arbitrator/prover/src/binary.rs @@ -1,9 +1,17 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::value::{ArbValueType, FunctionType, IntegerValType, Value as LirValue}; -use eyre::{bail, ensure, Result}; -use fnv::FnvHashMap as HashMap; +use crate::{ + programs::{ + config::CompileConfig, counter::Counter, depth::DepthChecker, dynamic::DynamicMeter, + heap::HeapBound, meter::Meter, start::StartMover, FuncMiddleware, Middleware, ModuleMod, + StylusData, STYLUS_ENTRY_POINT, + }, + value::{ArbValueType, FunctionType, IntegerValType, Value}, +}; +use arbutil::{math::SaturatingSum, Bytes32, Color, DebugColor}; +use eyre::{bail, ensure, eyre, Result, WrapErr}; +use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use nom::{ branch::alt, bytes::complete::tag, @@ -11,10 +19,11 @@ use nom::{ sequence::{preceded, tuple}, }; use serde::{Deserialize, Serialize}; -use std::{convert::TryInto, hash::Hash, str::FromStr}; +use std::{convert::TryInto, fmt::Debug, hash::Hash, mem, path::Path, str::FromStr}; +use wasmer_types::{entity::EntityRef, ExportIndex, FunctionIndex, LocalFunctionIndex}; use wasmparser::{ - Data, Element, Export, Global, Import, MemoryType, Name, NameSectionReader, Naming, Operator, - Parser, Payload, TableType, TypeDef, + Data, Element, ExternalKind, MemoryType, Name, NameSectionReader, Naming, Operator, Parser, + Payload, TableType, TypeRef, ValType, Validator, WasmFeatures, }; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -70,22 +79,16 @@ pub enum FloatInstruction { impl FloatInstruction { pub fn signature(&self) -> FunctionType { match *self { - FloatInstruction::UnOp(t, _) => FunctionType::new(vec![t.into()], vec![t.into()]), - FloatInstruction::BinOp(t, _) => FunctionType::new(vec![t.into(); 2], vec![t.into()]), - FloatInstruction::RelOp(t, _) => { - FunctionType::new(vec![t.into(); 2], vec![ArbValueType::I32]) - } - FloatInstruction::TruncIntOp(i, f, ..) => { - FunctionType::new(vec![f.into()], vec![i.into()]) - } - FloatInstruction::ConvertIntOp(f, i, _) => { - FunctionType::new(vec![i.into()], vec![f.into()]) - } + FloatInstruction::UnOp(t, _) => FunctionType::new([t.into()], [t.into()]), + FloatInstruction::BinOp(t, _) => FunctionType::new([t.into(); 2], [t.into()]), + FloatInstruction::RelOp(t, _) => FunctionType::new([t.into(); 2], [ArbValueType::I32]), + FloatInstruction::TruncIntOp(i, f, ..) => FunctionType::new([f.into()], [i.into()]), + FloatInstruction::ConvertIntOp(f, i, _) => FunctionType::new([i.into()], [f.into()]), FloatInstruction::F32DemoteF64 => { - FunctionType::new(vec![ArbValueType::F64], vec![ArbValueType::F32]) + FunctionType::new([ArbValueType::F64], [ArbValueType::F32]) } FloatInstruction::F64PromoteF32 => { - FunctionType::new(vec![ArbValueType::F32], vec![ArbValueType::F64]) + FunctionType::new([ArbValueType::F32], [ArbValueType::F64]) } } } @@ -202,16 +205,58 @@ impl FromStr for FloatInstruction { } } -pub fn op_as_const(op: Operator) -> Result { +pub fn op_as_const(op: Operator) -> Result { match op { - Operator::I32Const { value } => Ok(LirValue::I32(value as u32)), - Operator::I64Const { value } => Ok(LirValue::I64(value as u64)), - Operator::F32Const { value } => Ok(LirValue::F32(f32::from_bits(value.bits()))), - Operator::F64Const { value } => Ok(LirValue::F64(f64::from_bits(value.bits()))), + Operator::I32Const { value } => Ok(Value::I32(value as u32)), + Operator::I64Const { value } => Ok(Value::I64(value as u64)), + Operator::F32Const { value } => Ok(Value::F32(f32::from_bits(value.bits()))), + Operator::F64Const { value } => Ok(Value::F64(f64::from_bits(value.bits()))), _ => bail!("Opcode is not a constant"), } } +#[derive(Clone, Debug, Default)] +pub struct FuncImport<'a> { + pub offset: u32, + pub module: &'a str, + pub name: &'a str, +} + +/// This enum primarily exists because wasmer's ExternalKind doesn't impl these derived functions +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum ExportKind { + Func, + Table, + Memory, + Global, + Tag, +} + +impl From for ExportKind { + fn from(kind: ExternalKind) -> Self { + use ExternalKind as E; + match kind { + E::Func => Self::Func, + E::Table => Self::Table, + E::Memory => Self::Memory, + E::Global => Self::Global, + E::Tag => Self::Tag, + } + } +} + +impl From for ExportKind { + fn from(value: ExportIndex) -> Self { + use ExportIndex as E; + match value { + E::Function(_) => Self::Func, + E::Table(_) => Self::Table, + E::Memory(_) => Self::Memory, + E::Global(_) => Self::Global, + } + } +} + #[derive(Clone, Debug, Default)] pub struct Code<'a> { pub locals: Vec, @@ -230,24 +275,31 @@ pub struct NameCustomSection { pub functions: HashMap, } +pub type ExportMap = HashMap; + #[derive(Clone, Default)] pub struct WasmBinary<'a> { pub types: Vec, - pub imports: Vec>, + pub imports: Vec>, + /// Maps *local* function indices to global type signatures. pub functions: Vec, pub tables: Vec, pub memories: Vec, - pub globals: Vec>, - pub exports: Vec>, + pub globals: Vec, + pub exports: ExportMap, pub start: Option, pub elements: Vec>, pub codes: Vec>, pub datas: Vec>, pub names: NameCustomSection, + /// The source wasm, if known. + pub wasm: Option<&'a [u8]>, + /// Consensus data used to make module hashes unique. + pub extra_data: Vec, } -pub fn parse(input: &[u8]) -> eyre::Result> { - let features = wasmparser::WasmFeatures { +pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result> { + let features = WasmFeatures { mutable_global: true, saturating_float_to_int: true, sign_extension: true, @@ -258,42 +310,49 @@ pub fn parse(input: &[u8]) -> eyre::Result> { relaxed_simd: false, threads: false, tail_call: false, - deterministic_only: false, + floats: true, multi_memory: false, exceptions: false, memory64: false, extended_const: false, component_model: false, + function_references: false, + memory_control: false, + gc: false, + component_model_values: false, + component_model_nested_names: false, }; - wasmparser::Validator::new_with_features(features).validate_all(input)?; - let sections: Vec<_> = Parser::new(0).parse_all(input).collect::>()?; + Validator::new_with_features(features) + .validate_all(input) + .wrap_err_with(|| eyre!("failed to validate {}", path.to_string_lossy().red()))?; - let mut binary = WasmBinary::default(); + let mut binary = WasmBinary { + wasm: Some(input), + ..Default::default() + }; + let sections: Vec<_> = Parser::new(0).parse_all(input).collect::>()?; - for mut section in sections { + for section in sections { use Payload::*; macro_rules! process { ($dest:expr, $source:expr) => {{ - for _ in 0..$source.get_count() { - let item = $source.read()?; - $dest.push(item.into()) + for item in $source.into_iter() { + $dest.push(item?.into()) } }}; } - match &mut section { + match section { TypeSection(type_section) => { - for _ in 0..type_section.get_count() { - let TypeDef::Func(ty) = type_section.read()?; - binary.types.push(ty.try_into()?); + for func in type_section.into_iter_err_on_gc_types() { + binary.types.push(func?.try_into()?); } } CodeSectionEntry(codes) => { let mut code = Code::default(); let mut locals = codes.get_locals_reader()?; let mut ops = codes.get_operators_reader()?; - let mut index = 0; for _ in 0..locals.get_count() { @@ -312,35 +371,70 @@ pub fn parse(input: &[u8]) -> eyre::Result> { binary.codes.push(code); } - ImportSection(imports) => process!(binary.imports, imports), + GlobalSection(globals) => { + for global in globals { + let mut init = global?.init_expr.get_operators_reader(); + + let value = match (init.read()?, init.read()?, init.eof()) { + (op, Operator::End, true) => op_as_const(op)?, + _ => bail!("Non-constant global initializer"), + }; + binary.globals.push(value); + } + } + ImportSection(imports) => { + for import in imports { + let import = import?; + let TypeRef::Func(offset) = import.ty else { + bail!("unsupported import kind {:?}", import) + }; + let import = FuncImport { + offset, + module: import.module, + name: import.name, + }; + binary.imports.push(import); + } + } + ExportSection(exports) => { + use ExternalKind as E; + for export in exports { + let export = export?; + let name = export.name.to_owned(); + let kind = export.kind; + if let E::Func = kind { + let index = export.index; + let name = || name.clone(); + binary.names.functions.entry(index).or_insert_with(name); + } + binary.exports.insert(name, (export.index, kind.into())); + } + } FunctionSection(functions) => process!(binary.functions, functions), - TableSection(tables) => process!(binary.tables, tables), + TableSection(tables) => { + for table in tables { + binary.tables.push(table?.ty); + } + } MemorySection(memories) => process!(binary.memories, memories), - GlobalSection(globals) => process!(binary.globals, globals), - ExportSection(exports) => process!(binary.exports, exports), - StartSection { func, .. } => binary.start = Some(*func), + StartSection { func, .. } => binary.start = Some(func), ElementSection(elements) => process!(binary.elements, elements), DataSection(datas) => process!(binary.datas, datas), CodeSectionStart { .. } => {} - CustomSection { - name, - data_offset, - data, - .. - } => { - if *name != "name" { + CustomSection(reader) => { + if reader.name() != "name" { continue; } - let mut name_reader = NameSectionReader::new(data, *data_offset)?; + // CHECK: maybe reader.data_offset() + let name_reader = NameSectionReader::new(reader.data(), 0); - while !name_reader.eof() { - match name_reader.read()? { - Name::Module(name) => binary.names.module = name.get_name()?.to_owned(), + for name in name_reader { + match name? { + Name::Module { name, .. } => binary.names.module = name.to_owned(), Name::Function(namemap) => { - let mut map_reader = namemap.get_map()?; - for _ in 0..map_reader.get_count() { - let Naming { index, name } = map_reader.read()?; + for naming in namemap { + let Naming { index, name } = naming?; binary.names.functions.insert(index, name.to_owned()); } } @@ -348,12 +442,281 @@ pub fn parse(input: &[u8]) -> eyre::Result> { } } } - Version { num, .. } => ensure!(*num == 1, "wasm format version not supported {}", num), - UnknownSection { id, .. } => bail!("unsupported unknown section type {}", id), - End(_offset) => {} + Version { num, .. } => ensure!(num == 1, "wasm format version not supported {num}"), + UnknownSection { id, .. } => bail!("unsupported unknown section type {id}"), + End(_) => {} x => bail!("unsupported section type {:?}", x), } } + // reject the module if it imports the same func with inconsistent signatures + let mut imports = HashMap::default(); + for import in &binary.imports { + let offset = import.offset; + let module = import.module; + let name = import.name; + + let key = (module, name); + if let Some(prior) = imports.insert(key, offset) { + if prior != offset { + let name = name.debug_red(); + bail!("inconsistent imports for {} {name}", module.red()); + } + } + } + + // reject the module if it re-exports an import with the same name + let mut exports = HashSet::default(); + for export in binary.exports.keys() { + let export = export.rsplit("__").take(1); + exports.extend(export); + } + for import in &binary.imports { + let name = import.name; + if exports.contains(name) { + bail!("binary exports an import with the same name {}", name.red()); + } + } + + // reject the module if it imports or exports reserved symbols + let reserved = |x: &&str| x.starts_with("stylus"); + if let Some(name) = exports.into_iter().find(reserved) { + bail!("binary exports reserved symbol {}", name.red()) + } + if let Some(name) = binary.imports.iter().map(|x| x.name).find(reserved) { + bail!("binary imports reserved symbol {}", name.red()) + } + + // if no module name was given, make a best-effort guess with the file path + if binary.names.module.is_empty() { + binary.names.module = match path.file_name() { + Some(os_str) => os_str.to_string_lossy().into(), + None => path.to_string_lossy().into(), + }; + } Ok(binary) } + +impl<'a> Debug for WasmBinary<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WasmBinary") + .field("types", &self.types) + .field("imports", &self.imports) + .field("functions", &self.functions) + .field("tables", &self.tables) + .field("memories", &self.memories) + .field("globals", &self.globals) + .field("exports", &self.exports) + .field("start", &self.start) + .field("elements", &format!("<{} elements>", self.elements.len())) + .field("codes", &self.codes) + .field("datas", &self.datas) + .field("names", &self.names) + .finish() + } +} + +impl<'a> WasmBinary<'a> { + /// Instruments a user wasm, producing a version bounded via configurable instrumentation. + pub fn instrument( + &mut self, + compile: &CompileConfig, + codehash: &Bytes32, + ) -> Result { + let start = StartMover::new(compile.debug.debug_info); + let meter = Meter::new(&compile.pricing); + let dygas = DynamicMeter::new(&compile.pricing); + let depth = DepthChecker::new(compile.bounds); + let bound = HeapBound::new(compile.bounds); + + start.update_module(self)?; + meter.update_module(self)?; + dygas.update_module(self)?; + depth.update_module(self)?; + bound.update_module(self)?; + + let count = compile.debug.count_ops.then(Counter::new); + if let Some(count) = &count { + count.update_module(self)?; + } + + for (index, code) in self.codes.iter_mut().enumerate() { + let index = LocalFunctionIndex::from_u32(index as u32); + let locals: Vec = code.locals.iter().map(|x| x.value.into()).collect(); + + let mut build = mem::take(&mut code.expr); + let mut input = Vec::with_capacity(build.len()); + + /// this macro exists since middlewares aren't sized (can't use a vec without boxes) + macro_rules! apply { + ($middleware:expr) => { + let mut mid = Middleware::::instrument(&$middleware, index)?; + mid.locals_info(&locals); + + mem::swap(&mut build, &mut input); + + for op in input.drain(..) { + mid.feed(op, &mut build) + .wrap_err_with(|| format!("{} failure", mid.name()))? + } + }; + } + + // add the instrumentation in the order of application + // note: this must be consistent with native execution + apply!(start); + apply!(meter); + apply!(dygas); + apply!(depth); + apply!(bound); + + if let Some(count) = &count { + apply!(*count); + } + + code.expr = build; + } + + let wasm = self.wasm.take().unwrap(); + self.extra_data.extend(*codehash); + self.extra_data.extend(compile.version.to_be_bytes()); + + // 4GB maximum implies `footprint` fits in a u16 + let footprint = self.memory_info()?.min.0 as u16; + + // check the entrypoint + let ty = FunctionType::new([ArbValueType::I32], [ArbValueType::I32]); + let user_main = self.check_func(STYLUS_ENTRY_POINT, ty)?; + + // predict costs + let funcs = self.codes.len() as u64; + let globals = self.globals.len() as u64; + let wasm_len = wasm.len() as u64; + + let data_len: u64 = self.datas.iter().map(|x| x.range.len() as u64).sum(); + let elem_len: u64 = self.elements.iter().map(|x| x.range.len() as u64).sum(); + let data_len = data_len + elem_len; + + let mut type_len = 0; + for index in &self.functions { + let ty = &self.types[*index as usize]; + type_len += (ty.inputs.len() + ty.outputs.len()) as u64; + } + + let mut asm_estimate: u64 = 512000; + asm_estimate = asm_estimate.saturating_add(funcs.saturating_mul(996829) / 1000); + asm_estimate = asm_estimate.saturating_add(type_len.saturating_mul(11416) / 1000); + asm_estimate = asm_estimate.saturating_add(wasm_len.saturating_mul(62628) / 10000); + + let mut cached_init: u64 = 0; + cached_init = cached_init.saturating_add(funcs.saturating_mul(13420) / 100_000); + cached_init = cached_init.saturating_add(type_len.saturating_mul(89) / 100_000); + cached_init = cached_init.saturating_add(wasm_len.saturating_mul(122) / 100_000); + cached_init = cached_init.saturating_add(globals.saturating_mul(1628) / 1000); + cached_init = cached_init.saturating_add(data_len.saturating_mul(75244) / 100_000); + cached_init = cached_init.saturating_add(footprint as u64 * 5); + + let mut init = cached_init; + init = init.saturating_add(funcs.saturating_mul(8252) / 1000); + init = init.saturating_add(type_len.saturating_mul(1059) / 1000); + init = init.saturating_add(wasm_len.saturating_mul(1286) / 10_000); + + let [ink_left, ink_status] = meter.globals(); + let depth_left = depth.globals(); + Ok(StylusData { + ink_left: ink_left.as_u32(), + ink_status: ink_status.as_u32(), + depth_left: depth_left.as_u32(), + init_cost: init.try_into().wrap_err("init cost too high")?, + cached_init_cost: cached_init.try_into().wrap_err("cached cost too high")?, + asm_estimate: asm_estimate.try_into().wrap_err("asm estimate too large")?, + footprint, + user_main, + }) + } + + /// Parses and instruments a user wasm + pub fn parse_user( + wasm: &'a [u8], + page_limit: u16, + compile: &CompileConfig, + codehash: &Bytes32, + ) -> Result<(WasmBinary<'a>, StylusData)> { + let mut bin = parse(wasm, Path::new("user"))?; + let stylus_data = bin.instrument(compile, codehash)?; + + let Some(memory) = bin.memories.first() else { + bail!("missing memory with export name \"memory\"") + }; + let pages = memory.initial; + + // ensure the wasm fits within the remaining amount of memory + if pages > page_limit.into() { + let limit = page_limit.red(); + bail!("memory exceeds limit: {} > {limit}", pages.red()); + } + + // not strictly necessary, but anti-DoS limits and extra checks in case of bugs + macro_rules! limit { + ($limit:expr, $count:expr, $name:expr) => { + if $count > $limit { + bail!("too many wasm {}: {} > {}", $name, $count, $limit); + } + }; + } + limit!(1, bin.memories.len(), "memories"); + limit!(128, bin.datas.len(), "datas"); + limit!(128, bin.elements.len(), "elements"); + limit!(1024, bin.exports.len(), "exports"); + limit!(4096, bin.codes.len(), "functions"); + limit!(32768, bin.globals.len(), "globals"); + for code in &bin.codes { + limit!(348, code.locals.len(), "locals"); + limit!(65536, code.expr.len(), "opcodes in func body"); + } + + let table_entries = bin.tables.iter().map(|x| x.initial).saturating_sum(); + limit!(4096, table_entries, "table entries"); + + let elem_entries = bin.elements.iter().map(|x| x.range.len()).saturating_sum(); + limit!(4096, elem_entries, "element entries"); + + let max_len = 512; + macro_rules! too_long { + ($name:expr, $len:expr) => { + bail!( + "wasm {} too long: {} > {}", + $name.red(), + $len.red(), + max_len.red() + ) + }; + } + if let Some((name, _)) = bin.exports.iter().find(|(name, _)| name.len() > max_len) { + too_long!("name", name.len()) + } + if bin.names.module.len() > max_len { + too_long!("module name", bin.names.module.len()) + } + if bin.start.is_some() { + bail!("wasm start functions not allowed"); + } + Ok((bin, stylus_data)) + } + + /// Ensures a func exists and has the right type. + fn check_func(&self, name: &str, ty: FunctionType) -> Result { + let Some(&(func, kind)) = self.exports.get(name) else { + bail!("missing export with name {}", name.red()); + }; + if kind != ExportKind::Func { + let kind = kind.debug_red(); + bail!("export {} must be a function but is a {kind}", name.red()); + } + let func_ty = self.get_function(FunctionIndex::new(func.try_into()?))?; + if func_ty != ty { + bail!("wrong type for {}: {}", name.red(), func_ty.red()); + } + Ok(func) + } +} diff --git a/arbitrator/prover/src/bulk_memory.wat b/arbitrator/prover/src/bulk_memory.wat index 080290f15..bc0d12880 100644 --- a/arbitrator/prover/src/bulk_memory.wat +++ b/arbitrator/prover/src/bulk_memory.wat @@ -5,7 +5,7 @@ ;; https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md (module - (memory 0) + (memory (export "memory") 0 0) (func $memory_fill (param $dest i32) (param $value i32) (param $size i32) (local $value64 i64) ;; the bounds check happens before any data is written according to the spec diff --git a/arbitrator/prover/src/host.rs b/arbitrator/prover/src/host.rs index c0823b24a..d7e3a1736 100644 --- a/arbitrator/prover/src/host.rs +++ b/arbitrator/prover/src/host.rs @@ -1,22 +1,24 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -#![allow(clippy::vec_init_then_push)] +#![allow(clippy::vec_init_then_push, clippy::redundant_closure)] use crate::{ binary, host, machine::{Function, InboxIdentifier}, + programs::StylusData, utils, value::{ArbValueType, FunctionType}, wavm::{wasm_to_wavm, Instruction, Opcode}, }; -use arbutil::{Color, PreimageType}; +use arbutil::{evm::user::UserOutcomeKind, Color, PreimageType}; use eyre::{bail, ErrReport, Result}; use lazy_static::lazy_static; -use std::{collections::HashMap, str::FromStr}; +use num_derive::FromPrimitive; +use std::{collections::HashMap, path::Path, str::FromStr}; /// Represents the internal hostio functions a module may have. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, FromPrimitive)] #[repr(u64)] pub enum InternalFunc { WavmCallerLoad8, @@ -25,17 +27,38 @@ pub enum InternalFunc { WavmCallerStore32, MemoryFill, MemoryCopy, + UserInkLeft, + UserInkStatus, + UserSetInk, + UserStackLeft, + UserSetStack, + UserMemorySize, + CallMain, } impl InternalFunc { pub fn ty(&self) -> FunctionType { use ArbValueType::*; use InternalFunc::*; - match self { - WavmCallerLoad8 | WavmCallerLoad32 => FunctionType::new(vec![I32], vec![I32]), - WavmCallerStore8 | WavmCallerStore32 => FunctionType::new(vec![I32, I32], vec![]), - MemoryFill | MemoryCopy => FunctionType::new(vec![I32, I32, I32], vec![]), + macro_rules! func { + ([$($args:expr),*], [$($outs:expr),*]) => { + FunctionType::new(vec![$($args),*], vec![$($outs),*]) + }; } + #[rustfmt::skip] + let ty = match self { + WavmCallerLoad8 | WavmCallerLoad32 => func!([I32], [I32]), + WavmCallerStore8 | WavmCallerStore32 => func!([I32, I32], []), + MemoryFill | MemoryCopy => func!([I32, I32, I32], []), + UserInkLeft => func!([], [I64]), // λ() → ink_left + UserInkStatus => func!([], [I32]), // λ() → ink_status + UserSetInk => func!([I64, I32], []), // λ(ink_left, ink_status) + UserStackLeft => func!([], [I32]), // λ() → stack_left + UserSetStack => func!([I32], []), // λ(stack_left) + UserMemorySize => func!([], [I32]), // λ() → memory_size + CallMain => func!([I32], [I32]), // λ(args_len) → status + }; + ty } } @@ -56,6 +79,29 @@ pub enum Hostio { WavmReadInboxMessage, WavmReadDelayedInboxMessage, WavmHaltAndSetFinished, + WavmLinkModule, + WavmUnlinkModule, + ProgramInkLeft, + ProgramInkStatus, + ProgramSetInk, + ProgramStackLeft, + ProgramSetStack, + ProgramMemorySize, + ProgramCallMain, + ProgramRequest, + ProgramContinue, + ConsoleLogTxt, + ConsoleLogI32, + ConsoleLogI64, + ConsoleLogF32, + ConsoleLogF64, + ConsoleTeeI32, + ConsoleTeeI64, + ConsoleTeeF32, + ConsoleTeeF64, + UserInkLeft, + UserInkStatus, + UserSetInk, } impl FromStr for Hostio { @@ -81,6 +127,29 @@ impl FromStr for Hostio { ("env", "wavm_read_inbox_message") => WavmReadInboxMessage, ("env", "wavm_read_delayed_inbox_message") => WavmReadDelayedInboxMessage, ("env", "wavm_halt_and_set_finished") => WavmHaltAndSetFinished, + ("hostio", "wavm_link_module") => WavmLinkModule, + ("hostio", "wavm_unlink_module") => WavmUnlinkModule, + ("hostio", "program_ink_left") => ProgramInkLeft, + ("hostio", "program_ink_status") => ProgramInkStatus, + ("hostio", "program_set_ink") => ProgramSetInk, + ("hostio", "program_stack_left") => ProgramStackLeft, + ("hostio", "program_set_stack") => ProgramSetStack, + ("hostio", "program_memory_size") => ProgramMemorySize, + ("hostio", "program_call_main") => ProgramCallMain, + ("hostio", "program_request") => ProgramRequest, + ("hostio", "program_continue") => ProgramContinue, + ("hostio", "user_ink_left") => UserInkLeft, + ("hostio", "user_ink_status") => UserInkStatus, + ("hostio", "user_set_ink") => UserSetInk, + ("console", "log_txt") => ConsoleLogTxt, + ("console", "log_i32") => ConsoleLogI32, + ("console", "log_i64") => ConsoleLogI64, + ("console", "log_f32") => ConsoleLogF32, + ("console", "log_f64") => ConsoleLogF64, + ("console", "tee_i32") => ConsoleTeeI32, + ("console", "tee_i64") => ConsoleTeeI64, + ("console", "tee_f32") => ConsoleTeeF32, + ("console", "tee_f64") => ConsoleTeeF64, _ => bail!("no such hostio {} in {}", name.red(), module.red()), }) } @@ -120,11 +189,34 @@ impl Hostio { WavmReadInboxMessage => func!([I64, I32, I32], [I32]), WavmReadDelayedInboxMessage => func!([I64, I32, I32], [I32]), WavmHaltAndSetFinished => func!(), + WavmLinkModule => func!([I32], [I32]), // λ(module_hash) → module + WavmUnlinkModule => func!(), // λ() + ProgramInkLeft => func!([I32], [I64]), // λ(module) → ink_left + ProgramInkStatus => func!([I32], [I32]), // λ(module) → ink_status + ProgramSetInk => func!([I32, I64]), // λ(module, ink_left) + ProgramStackLeft => func!([I32], [I32]), // λ(module) → stack_left + ProgramSetStack => func!([I32, I32]), // λ(module, stack_left) + ProgramMemorySize => func!([I32], [I32]), // λ(module) → memory_size + ProgramCallMain => func!([I32, I32], [I32]), // λ(module, args_len) → status + ProgramRequest => func!([I32], [I32]), // λ(status) → response + ProgramContinue => func!([I32], [I32]), // λ(response) → status + ConsoleLogTxt => func!([I32, I32]), // λ(text, len) + ConsoleLogI32 => func!([I32]), // λ(value) + ConsoleLogI64 => func!([I64]), // λ(value) + ConsoleLogF32 => func!([F32]), // λ(value) + ConsoleLogF64 => func!([F64]), // λ(value) + ConsoleTeeI32 => func!([I32], [I32]), // λ(value) → value + ConsoleTeeI64 => func!([I64], [I64]), // λ(value) → value + ConsoleTeeF32 => func!([F32], [F32]), // λ(value) → value + ConsoleTeeF64 => func!([F64], [F64]), // λ(value) → value + UserInkLeft => InternalFunc::UserInkLeft.ty(), + UserInkStatus => InternalFunc::UserInkStatus.ty(), + UserSetInk => InternalFunc::UserSetInk.ty(), }; ty } - pub fn body(&self) -> Vec { + pub fn body(&self, _prior: usize) -> Vec { let mut body = vec![]; macro_rules! opcode { @@ -135,27 +227,38 @@ impl Hostio { body.push(Instruction::with_data($opcode, $value as u64)) }; } + macro_rules! cross_internal { + ($func:ident) => { + opcode!(LocalGet, 0); // module + opcode!(CrossModuleInternalCall, InternalFunc::$func); // consumes module + }; + } + macro_rules! intern { + ($func:ident) => { + opcode!(CallerModuleInternalCall, InternalFunc::$func); + }; + } use Hostio::*; use Opcode::*; match self { WavmCallerLoad8 => { opcode!(LocalGet, 0); - opcode!(CallerModuleInternalCall, InternalFunc::WavmCallerLoad8); + intern!(WavmCallerLoad8); } WavmCallerLoad32 => { opcode!(LocalGet, 0); - opcode!(CallerModuleInternalCall, InternalFunc::WavmCallerLoad32); + intern!(WavmCallerLoad32); } WavmCallerStore8 => { opcode!(LocalGet, 0); opcode!(LocalGet, 1); - opcode!(CallerModuleInternalCall, InternalFunc::WavmCallerStore8); + intern!(WavmCallerStore8); } WavmCallerStore32 => { opcode!(LocalGet, 0); opcode!(LocalGet, 1); - opcode!(CallerModuleInternalCall, InternalFunc::WavmCallerStore32); + intern!(WavmCallerStore32); } WavmGetGlobalStateBytes32 => { opcode!(LocalGet, 0); @@ -211,37 +314,134 @@ impl Hostio { WavmHaltAndSetFinished => { opcode!(HaltAndSetFinished); } + WavmLinkModule => { + // λ(module_hash) → module + opcode!(LocalGet, 0); + opcode!(LinkModule); + opcode!(NewCoThread); + } + WavmUnlinkModule => { + // λ() + opcode!(UnlinkModule); + opcode!(PopCoThread); + } + ProgramInkLeft => { + // λ(module) → ink_left + cross_internal!(UserInkLeft); + } + ProgramInkStatus => { + // λ(module) → ink_status + cross_internal!(UserInkStatus); + } + ProgramSetInk => { + // λ(module, ink_left) + opcode!(LocalGet, 1); // ink_left + opcode!(I32Const, 0); // ink_status + cross_internal!(UserSetInk); + } + ProgramStackLeft => { + // λ(module) → stack_left + cross_internal!(UserStackLeft); + } + ProgramSetStack => { + // λ(module, stack_left) + opcode!(LocalGet, 1); // stack_left + cross_internal!(UserSetStack); + } + ProgramMemorySize => { + // λ(module) → memory_size + cross_internal!(UserMemorySize); + } + ProgramCallMain => { + // caller sees: λ(module, args_len) → status + opcode!(LocalGet, 1); // args_len + opcode!(LocalGet, 0); // module + opcode!(MoveFromStackToInternal); + opcode!(MoveFromStackToInternal); + opcode!(SwitchThread, 8); + opcode!(MoveFromInternalToStack); + opcode!(MoveFromInternalToStack); + opcode!(CrossModuleInternalCall, InternalFunc::CallMain); // consumes module + opcode!(MoveFromStackToInternal); + opcode!(SwitchThread, 0); + opcode!(MoveFromInternalToStack); + opcode!(Return); + + // jumps here if errored while in thread 1 + opcode!(I32Const, UserOutcomeKind::Failure as u32) + } + ProgramContinue => { + // caller sees: λ(return_data) → status (returns to caller of ProgramRequest) + // code returns return_data to caller of ProgramRequest + opcode!(LocalGet, 0); // return_data + opcode!(MoveFromStackToInternal); + opcode!(SwitchThread, 3); + opcode!(MoveFromInternalToStack); + opcode!(Return); + + // jumps here if errored while in cothread + opcode!(I32Const, UserOutcomeKind::Failure as u32) + } + ProgramRequest => { + // caller sees: λ(status) → response + // code returns status of either ProgramContinue or ProgramCallMain + opcode!(LocalGet, 0); // return_data + opcode!(MoveFromStackToInternal); + opcode!(SwitchThread, 0); + opcode!(MoveFromInternalToStack); + } + UserInkLeft => { + // λ() → ink_left + intern!(UserInkLeft); + } + UserInkStatus => { + // λ() → ink_status + intern!(UserInkStatus); + } + UserSetInk => { + // λ(ink_left, ink_status) + opcode!(LocalGet, 0); + opcode!(LocalGet, 1); + intern!(UserSetInk); + } + ConsoleLogTxt | ConsoleLogI32 | ConsoleLogI64 | ConsoleLogF32 | ConsoleLogF64 => {} + ConsoleTeeI32 | ConsoleTeeI64 | ConsoleTeeF32 | ConsoleTeeF64 => { + opcode!(LocalGet, 0); + } } body } } -pub fn get_impl(module: &str, name: &str) -> Result { +pub fn get_impl(module: &str, name: &str) -> Result<(Function, bool)> { let hostio: Hostio = format!("{module}__{name}").parse()?; let append = |code: &mut Vec| { - code.extend(hostio.body()); + let len = code.len(); + code.extend(hostio.body(len)); Ok(()) }; - Function::new(&[], append, hostio.ty(), &[]) + + let debug = module == "console" || module == "debug"; + Function::new(&[], append, hostio.ty(), &[]).map(|x| (x, debug)) } /// Adds internal functions to a module. /// Note: the order of the functions must match that of the `InternalFunc` enum -pub fn new_internal_funcs() -> Vec { +pub fn new_internal_funcs(stylus_data: Option) -> Vec { use ArbValueType::*; use InternalFunc::*; use Opcode::*; - fn code_func(code: Vec, ty: FunctionType) -> Function { + fn code_func(code: &[Instruction], func: InternalFunc) -> Function { let mut wavm = vec![Instruction::simple(InitFrame)]; wavm.extend(code); wavm.push(Instruction::simple(Return)); - Function::new_from_wavm(wavm, ty, vec![]) + Function::new_from_wavm(wavm, func.ty(), vec![]) } fn op_func(opcode: Opcode, func: InternalFunc) -> Function { - code_func(vec![Instruction::simple(opcode)], func.ty()) + code_func(&[Instruction::simple(opcode)], func) } let mut funcs = vec![]; @@ -274,6 +474,30 @@ pub fn new_internal_funcs() -> Vec { let [memory_fill, memory_copy] = (*BULK_MEMORY_FUNCS).clone(); add_func(memory_fill, MemoryFill); add_func(memory_copy, MemoryCopy); + + let mut add_func = |code: &[_], internal| add_func(code_func(code, internal), internal); + + if let Some(info) = stylus_data { + let (gas, status, depth) = info.global_offsets(); + let main = info.user_main.into(); + let inst = |op, data| Instruction::with_data(op, data); + + add_func(&[inst(GlobalGet, gas)], UserInkLeft); + add_func(&[inst(GlobalGet, status)], UserInkStatus); + add_func(&[inst(GlobalSet, status), inst(GlobalSet, gas)], UserSetInk); + add_func(&[inst(GlobalGet, depth)], UserStackLeft); + add_func(&[inst(GlobalSet, depth)], UserSetStack); + add_func(&[inst(MemorySize, 0)], UserMemorySize); + add_func( + &[ + inst(Call, main), + // force return value to boolean + Instruction::simple(I32Eqz), + Instruction::simple(I32Eqz), + ], + CallMain, + ); + } funcs } @@ -283,7 +507,7 @@ lazy_static! { let data = include_bytes!("bulk_memory.wat"); let wasm = wat::parse_bytes(data).expect("failed to parse bulk_memory.wat"); - let bin = binary::parse(&wasm).expect("failed to parse bulk_memory.wasm"); + let bin = binary::parse(&wasm, Path::new("internal")).expect("failed to parse bulk_memory.wasm"); let types = [MemoryFill.ty(), MemoryCopy.ty()]; let names = ["memory_fill", "memory_copy"]; @@ -304,6 +528,7 @@ lazy_static! { &[ty.clone()], // only type needed is the func itself 0, // ----------------------------------- 0, // impls don't use other internals + &bin.names.module, ), ty.clone(), &[] // impls don't make calls diff --git a/arbitrator/prover/src/kzg.rs b/arbitrator/prover/src/kzg.rs index 66a71c52b..b3348b904 100644 --- a/arbitrator/prover/src/kzg.rs +++ b/arbitrator/prover/src/kzg.rs @@ -1,7 +1,7 @@ // Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::utils::Bytes32; +use arbutil::Bytes32; use c_kzg::{ KzgSettings, BYTES_PER_BLOB, BYTES_PER_G1_POINT, BYTES_PER_G2_POINT, FIELD_ELEMENTS_PER_BLOB, }; diff --git a/arbitrator/prover/src/kzgbn254.rs b/arbitrator/prover/src/kzgbn254.rs index 9b947ff02..7d97ca29d 100644 --- a/arbitrator/prover/src/kzgbn254.rs +++ b/arbitrator/prover/src/kzgbn254.rs @@ -1,4 +1,4 @@ -use crate::utils::Bytes32; +use crate::Bytes32; use ark_bn254::G2Affine; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{BigInteger, PrimeField}; diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index 823f44875..379dc4268 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -1,36 +1,54 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE #![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] pub mod binary; mod host; +#[cfg(feature = "native")] mod kzg; mod kzgbn254; pub mod machine; /// cbindgen:ignore mod memory; mod merkle; +mod print; +pub mod programs; mod reinterpret; pub mod utils; pub mod value; pub mod wavm; -use crate::machine::{argument_data_to_inbox, Machine}; -use arbutil::PreimageType; -use eyre::Result; -use machine::{get_empty_preimage_resolver, GlobalState, MachineStatus, PreimageResolver}; +#[cfg(test)] +mod test; + +pub use machine::Machine; + +use arbutil::{Bytes32, PreimageType}; +use eyre::{Report, Result}; +use lru::LruCache; +use machine::{ + argument_data_to_inbox, get_empty_preimage_resolver, GlobalState, MachineStatus, + PreimageResolver, +}; +use once_cell::sync::OnceCell; use static_assertions::const_assert_eq; use std::{ ffi::CStr, + num::NonZeroUsize, os::raw::{c_char, c_int}, path::Path, + ptr, slice, sync::{ atomic::{self, AtomicU8}, - Arc, + Arc, Mutex, }, }; -use utils::{Bytes32, CBytes}; +use utils::CBytes; + +lazy_static::lazy_static! { + static ref BLOBHASH_PREIMAGE_CACHE: Mutex>>> = Mutex::new(LruCache::new(NonZeroUsize::new(12).unwrap())); +} #[repr(C)] #[derive(Clone, Copy)] @@ -52,12 +70,15 @@ pub unsafe extern "C" fn arbitrator_load_machine( binary_path: *const c_char, library_paths: *const *const c_char, library_paths_size: isize, + debug_chain: usize, ) -> *mut Machine { - match arbitrator_load_machine_impl(binary_path, library_paths, library_paths_size) { + let debug_chain = debug_chain != 0; + match arbitrator_load_machine_impl(binary_path, library_paths, library_paths_size, debug_chain) + { Ok(mach) => mach, Err(err) => { - eprintln!("Error loading binary: {}", err); - std::ptr::null_mut() + eprintln!("Error loading binary: {:?}", err); + ptr::null_mut() } } } @@ -66,6 +87,7 @@ unsafe fn arbitrator_load_machine_impl( binary_path: *const c_char, library_paths: *const *const c_char, library_paths_size: isize, + debug_chain: bool, ) -> Result<*mut Machine> { let binary_path = cstr_to_string(binary_path); let binary_path = Path::new(&binary_path); @@ -82,6 +104,8 @@ unsafe fn arbitrator_load_machine_impl( true, false, false, + debug_chain, + debug_chain, Default::default(), Default::default(), get_empty_preimage_resolver(), @@ -90,14 +114,15 @@ unsafe fn arbitrator_load_machine_impl( } #[no_mangle] +#[cfg(feature = "native")] pub unsafe extern "C" fn arbitrator_load_wavm_binary(binary_path: *const c_char) -> *mut Machine { let binary_path = cstr_to_string(binary_path); let binary_path = Path::new(&binary_path); match Machine::new_from_wavm(binary_path) { Ok(mach) => Box::into_raw(Box::new(mach)), Err(err) => { - eprintln!("Error loading binary: {}", err); - std::ptr::null_mut() + eprintln!("Error loading binary: {err}"); + ptr::null_mut() } } } @@ -106,6 +131,23 @@ unsafe fn cstr_to_string(c_str: *const c_char) -> String { CStr::from_ptr(c_str).to_string_lossy().into_owned() } +pub fn err_to_c_string(err: Report) -> *mut libc::c_char { + str_to_c_string(&format!("{err:?}")) +} + +/// Copies the str-data into a libc free-able C string +pub fn str_to_c_string(text: &str) -> *mut libc::c_char { + unsafe { + let buf = libc::malloc(text.len() + 1); // includes null-terminating byte + if buf.is_null() { + panic!("Failed to allocate memory for error string"); + } + ptr::copy_nonoverlapping(text.as_ptr(), buf as *mut u8, text.len()); + *(buf as *mut u8).add(text.len()) = 0; + buf as *mut libc::c_char + } +} + #[no_mangle] pub unsafe extern "C" fn arbitrator_free_machine(mach: *mut Machine) { drop(Box::from_raw(mach)); @@ -123,22 +165,10 @@ pub unsafe extern "C" fn atomic_u8_store(ptr: *mut u8, contents: u8) { (*(ptr as *mut AtomicU8)).store(contents, atomic::Ordering::Relaxed); } -fn err_to_c_string(err: eyre::Report) -> *mut libc::c_char { - let err = format!("{:#}", err); - unsafe { - let buf = libc::malloc(err.len() + 1); - if buf.is_null() { - panic!("Failed to allocate memory for error string"); - } - std::ptr::copy_nonoverlapping(err.as_ptr(), buf as *mut u8, err.len()); - *(buf.add(err.len()) as *mut u8) = 0; - buf as *mut libc::c_char - } -} - /// Runs the machine while the condition variable is zero. May return early if num_steps is hit. /// Returns a c string error (freeable with libc's free) on error, or nullptr on success. #[no_mangle] +#[cfg(feature = "native")] pub unsafe extern "C" fn arbitrator_step( mach: *mut Machine, num_steps: u64, @@ -158,7 +188,7 @@ pub unsafe extern "C" fn arbitrator_step( } remaining_steps -= stepping; } - std::ptr::null_mut() + ptr::null_mut() } #[no_mangle] @@ -170,7 +200,7 @@ pub unsafe extern "C" fn arbitrator_add_inbox_message( ) -> c_int { let mach = &mut *mach; if let Some(identifier) = argument_data_to_inbox(inbox_identifier) { - let slice = std::slice::from_raw_parts(data.ptr, data.len); + let slice = slice::from_raw_parts(data.ptr, data.len); let data = slice.to_vec(); mach.add_inbox_msg(identifier, index, data); 0 @@ -179,9 +209,22 @@ pub unsafe extern "C" fn arbitrator_add_inbox_message( } } +/// Adds a user program to the machine's known set of wasms. +#[no_mangle] +pub unsafe extern "C" fn arbitrator_add_user_wasm( + mach: *mut Machine, + module: *const u8, + module_len: usize, + module_hash: *const Bytes32, +) { + let module = slice::from_raw_parts(module, module_len); + (*mach).add_stylus_module(*module_hash, module.to_owned()); +} + /// Like arbitrator_step, but stops early if it hits a host io operation. /// Returns a c string error (freeable with libc's free) on error, or nullptr on success. #[no_mangle] +#[cfg(feature = "native")] pub unsafe extern "C" fn arbitrator_step_until_host_io( mach: *mut Machine, condition: *const u8, @@ -191,10 +234,10 @@ pub unsafe extern "C" fn arbitrator_step_until_host_io( while condition.load(atomic::Ordering::Relaxed) == 0 { for _ in 0..1_000_000 { if mach.is_halted() { - return std::ptr::null_mut(); + return ptr::null_mut(); } if mach.next_instruction_is_host_io() { - return std::ptr::null_mut(); + return ptr::null_mut(); } match mach.step_n(1) { Ok(()) => {} @@ -202,7 +245,7 @@ pub unsafe extern "C" fn arbitrator_step_until_host_io( } } } - std::ptr::null_mut() + ptr::null_mut() } #[no_mangle] @@ -213,7 +256,7 @@ pub unsafe extern "C" fn arbitrator_serialize_state( let mach = &*mach; let res = CStr::from_ptr(path) .to_str() - .map_err(eyre::Report::from) + .map_err(Report::from) .and_then(|path| mach.serialize_state(path)); if let Err(err) = res { eprintln!("Failed to serialize machine state: {}", err); @@ -231,7 +274,7 @@ pub unsafe extern "C" fn arbitrator_deserialize_and_replace_state( let mach = &mut *mach; let res = CStr::from_ptr(path) .to_str() - .map_err(eyre::Report::from) + .map_err(Report::from) .and_then(|path| mach.deserialize_and_replace_state(path)); if let Err(err) = res { eprintln!("Failed to deserialize machine state: {}", err); @@ -291,32 +334,55 @@ pub struct ResolvedPreimage { pub len: isize, // negative if not found } +#[cfg(feature = "native")] +unsafe fn handle_preimage_resolution( + context: u64, + ty: PreimageType, + hash: Bytes32, + resolver: unsafe extern "C" fn(u64, u8, *const u8) -> ResolvedPreimage, +) -> Option { + let res = resolver(context, ty.into(), hash.as_ptr()); + if res.len < 0 { + return None; + } + let data = CBytes::from_raw_parts(res.ptr, res.len as usize); + // Check if preimage rehashes to the provided hash + match crate::utils::hash_preimage(&data, ty) { + Ok(have_hash) if have_hash.as_slice() == *hash => {} + Ok(got_hash) => panic!( + "Resolved incorrect data for hash {} (rehashed to {})", + hash, + Bytes32(got_hash), + ), + Err(err) => panic!( + "Failed to hash preimage from resolver (expecting hash {}): {}", + hash, err, + ), + } + Some(data) +} + #[no_mangle] +#[cfg(feature = "native")] pub unsafe extern "C" fn arbitrator_set_preimage_resolver( mach: *mut Machine, resolver: unsafe extern "C" fn(u64, u8, *const u8) -> ResolvedPreimage, ) { (*mach).set_preimage_resolver(Arc::new( move |context: u64, ty: PreimageType, hash: Bytes32| -> Option { - let res = resolver(context, ty.into(), hash.as_ptr()); - if res.len < 0 { - return None; - } - let data = CBytes::from_raw_parts(res.ptr, res.len as usize); - #[cfg(debug_assertions)] - match crate::utils::hash_preimage(&data, ty) { - Ok(have_hash) if have_hash.as_slice() == *hash => {} - Ok(got_hash) => panic!( - "Resolved incorrect data for hash {} (rehashed to {})", - hash, - Bytes32(got_hash), - ), - Err(err) => panic!( - "Failed to hash preimage from resolver (expecting hash {}): {}", - hash, err, - ), + if ty == PreimageType::EthVersionedHash { + let cache: Arc> = { + let mut locked = BLOBHASH_PREIMAGE_CACHE.lock().unwrap(); + locked.get_or_insert(hash, Default::default).clone() + }; + return cache + .get_or_try_init(|| { + handle_preimage_resolution(context, ty, hash, resolver).ok_or(()) + }) + .ok() + .cloned(); } - Some(data) + handle_preimage_resolution(context, ty, hash, resolver) }, ) as PreimageResolver); } @@ -327,16 +393,17 @@ pub unsafe extern "C" fn arbitrator_set_context(mach: *mut Machine, context: u64 } #[no_mangle] -pub unsafe extern "C" fn arbitrator_hash(mach: *mut Machine) -> utils::Bytes32 { +pub unsafe extern "C" fn arbitrator_hash(mach: *mut Machine) -> Bytes32 { (*mach).hash() } #[no_mangle] -pub unsafe extern "C" fn arbitrator_module_root(mach: *mut Machine) -> utils::Bytes32 { +pub unsafe extern "C" fn arbitrator_module_root(mach: *mut Machine) -> Bytes32 { (*mach).get_modules_root() } #[no_mangle] +#[cfg(feature = "native")] pub unsafe extern "C" fn arbitrator_gen_proof(mach: *mut Machine) -> RustByteArray { let mut proof = (*mach).serialize_proof(); let ret = RustByteArray { diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index b332593fa..d54e2d922 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -1,28 +1,34 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +#[cfg(feature = "native")] +use crate::kzg::prove_kzg_preimage; use crate::{ - binary::{parse, FloatInstruction, Local, NameCustomSection, WasmBinary}, + binary::{ + self, parse, ExportKind, ExportMap, FloatInstruction, Local, NameCustomSection, WasmBinary, + }, host, - kzg::prove_kzg_preimage, kzgbn254::prove_kzg_preimage_bn254, memory::Memory, merkle::{Merkle, MerkleType}, + programs::{config::CompileConfig, meter::MeteredMachine, ModuleMod, StylusData}, reinterpret::{ReinterpretAsSigned, ReinterpretAsUnsigned}, - utils::{file_bytes, Bytes32, CBytes, RemoteTableType}, + utils::{file_bytes, CBytes, RemoteTableType}, value::{ArbValueType, FunctionType, IntegerValType, ProgramCounter, Value}, wavm::{ - pack_cross_module_call, unpack_cross_module_call, wasm_to_wavm, FloatingPointImpls, + self, pack_cross_module_call, unpack_cross_module_call, wasm_to_wavm, FloatingPointImpls, IBinOpType, IRelOpType, IUnOpType, Instruction, Opcode, }, }; -use arbutil::{Color, PreimageType}; +use arbutil::{crypto, math, Bytes32, Color, DebugColor, PreimageType}; +use brotli::Dictionary; +#[cfg(feature = "native")] use c_kzg::BYTES_PER_BLOB; use digest::Digest; use eyre::{bail, ensure, eyre, Result, WrapErr}; use fnv::FnvHashMap as HashMap; +use lazy_static::lazy_static; use num::{traits::PrimInt, Zero}; -use rayon::prelude::*; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use sha3::Keccak256; @@ -32,12 +38,18 @@ use std::{ convert::{TryFrom, TryInto}, fmt::{self, Display}, fs::File, + hash::Hash, io::{BufReader, BufWriter, Write}, num::Wrapping, + ops::Add, path::{Path, PathBuf}, sync::Arc, }; -use wasmparser::{DataKind, ElementItem, ElementKind, ExternalKind, Operator, TableType, TypeRef}; +use wasmer_types::FunctionIndex; +use wasmparser::{DataKind, ElementItems, ElementKind, Operator, RefType, TableType}; + +#[cfg(feature = "rayon")] +use rayon::prelude::*; fn hash_call_indirect_data(table: u32, ty: &FunctionType) -> Bytes32 { let mut h = Keccak256::new(); @@ -63,11 +75,11 @@ pub fn argument_data_to_inbox(argument_data: u64) -> Option { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Function { - code: Vec, - ty: FunctionType, + pub code: Vec, + pub ty: FunctionType, #[serde(skip)] code_merkle: Merkle, - local_types: Vec, + pub local_types: Vec, } impl Function { @@ -107,7 +119,7 @@ impl Function { // Insert missing proving argument data for inst in insts.iter_mut() { if inst.opcode == Opcode::CallIndirect { - let (table, ty) = crate::wavm::unpack_call_indirect(inst.argument_data); + let (table, ty) = wavm::unpack_call_indirect(inst.argument_data); let ty = &module_types[usize::try_from(ty).unwrap()]; inst.proving_argument_data = Some(hash_call_indirect_data(table, ty)); } @@ -125,17 +137,36 @@ impl Function { u32::try_from(code.len()).is_ok(), "Function instruction count doesn't fit in a u32", ); - let code_merkle = Merkle::new( - MerkleType::Instruction, - code.par_iter().map(|i| i.hash()).collect(), - ); - - Function { + let mut func = Function { code, ty, - code_merkle, + code_merkle: Merkle::default(), // TODO: make an option local_types, - } + }; + func.set_code_merkle(); + func + } + + const CHUNK_SIZE: usize = 64; + + fn set_code_merkle(&mut self) { + let code = &self.code; + let chunks = math::div_ceil::<64>(code.len()); + let crunch = |x: usize| Instruction::hash(&code[64 * x..(64 * (x + 1)).min(code.len())]); + + #[cfg(feature = "rayon")] + let code_hashes = (0..chunks).into_par_iter().map(crunch).collect(); + + #[cfg(not(feature = "rayon"))] + let code_hashes = (0..chunks).map(crunch).collect(); + + self.code_merkle = Merkle::new(MerkleType::Instruction, code_hashes); + } + + fn serialize_body_for_proof(&self, pc: ProgramCounter) -> Vec { + let start = pc.inst() / 64 * 64; + let end = (start + 64).min(self.code.len()); + Instruction::serialize_for_proof(&self.code[start..end]) } fn hash(&self) -> Bytes32 { @@ -188,9 +219,9 @@ impl StackFrame { } #[derive(Clone, Debug, Serialize, Deserialize)] -struct TableElement { +pub(crate) struct TableElement { func_ty: FunctionType, - val: Value, + pub val: Value, } impl Default for TableElement { @@ -214,10 +245,10 @@ impl TableElement { #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] -struct Table { +pub(crate) struct Table { #[serde(with = "RemoteTableType")] - ty: TableType, - elems: Vec, + pub ty: TableType, + pub elems: Vec, #[serde(skip)] elems_merkle: Merkle, } @@ -247,82 +278,137 @@ struct AvailableImport { func: u32, } +impl AvailableImport { + pub fn new(ty: FunctionType, module: u32, func: u32) -> Self { + Self { ty, module, func } + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize)] -struct Module { - globals: Vec, - memory: Memory, - tables: Vec, +pub struct Module { + pub(crate) globals: Vec, + pub(crate) memory: Memory, + pub(crate) tables: Vec
, #[serde(skip)] - tables_merkle: Merkle, - funcs: Arc>, + pub(crate) tables_merkle: Merkle, + pub(crate) funcs: Arc>, #[serde(skip)] - funcs_merkle: Arc, - types: Arc>, - internals_offset: u32, - names: Arc, - host_call_hooks: Arc>>, - start_function: Option, - func_types: Arc>, - exports: Arc>, + pub(crate) funcs_merkle: Arc, + pub(crate) types: Arc>, + pub(crate) internals_offset: u32, + pub(crate) names: Arc, + pub(crate) host_call_hooks: Arc>>, + pub(crate) start_function: Option, + pub(crate) func_types: Arc>, + /// Old modules use this format. + /// TODO: remove this after the jump to stylus. + #[serde(alias = "exports")] + pub(crate) func_exports: Arc>, + #[serde(default)] + pub(crate) all_exports: Arc, + /// Used to make modules unique. + pub(crate) extra_hash: Arc, +} + +lazy_static! { + static ref USER_IMPORTS: HashMap = { + let mut imports = HashMap::default(); + + let forward = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); + let forward = binary::parse(forward, Path::new("forward")).unwrap(); + + for (name, &(export, kind)) in &forward.exports { + if kind == ExportKind::Func { + let ty = match forward.get_function(FunctionIndex::from_u32(export)) { + Ok(ty) => ty, + Err(error) => panic!("failed to read export {name}: {error:?}"), + }; + let import = AvailableImport::new(ty, 1, export); + imports.insert(name.to_owned(), import); + } + } + imports + }; } impl Module { + const FORWARDING_PREFIX: &'static str = "arbitrator_forward__"; + fn from_binary( bin: &WasmBinary, available_imports: &HashMap, floating_point_impls: &FloatingPointImpls, allow_hostapi: bool, + debug_funcs: bool, + stylus_data: Option, ) -> Result { let mut code = Vec::new(); let mut func_type_idxs: Vec = Vec::new(); let mut memory = Memory::default(); - let mut exports = HashMap::default(); let mut tables = Vec::new(); let mut host_call_hooks = Vec::new(); + let bin_name = &bin.names.module; for import in &bin.imports { - if let TypeRef::Func(ty) = import.ty { - let mut qualified_name = format!("{}__{}", import.module, import.name); - qualified_name = qualified_name.replace(&['/', '.'] as &[char], "_"); - let have_ty = &bin.types[ty as usize]; - let func; - if let Some(import) = available_imports.get(&qualified_name) { - ensure!( - &import.ty == have_ty, - "Import has different function signature than host function. Expected {:?} but got {:?}", - import.ty, have_ty, - ); - let wavm = vec![ - Instruction::simple(Opcode::InitFrame), - Instruction::with_data( - Opcode::CrossModuleCall, - pack_cross_module_call(import.module, import.func), - ), - Instruction::simple(Opcode::Return), - ]; - func = Function::new_from_wavm(wavm, import.ty.clone(), Vec::new()); - } else { - func = host::get_impl(import.module, import.name)?; - ensure!( - &func.ty == have_ty, - "Import has different function signature than host function. Expected {:?} but got {:?}", - func.ty, have_ty, - ); - ensure!( - allow_hostapi, - "Calling hostapi directly is not allowed. Function {}", - import.name, - ); - } - func_type_idxs.push(ty); - code.push(func); - host_call_hooks.push(Some((import.module.into(), import.name.into()))); + let module = import.module; + let have_ty = &bin.types[import.offset as usize]; + let (forward, import_name) = match import.name.strip_prefix(Module::FORWARDING_PREFIX) { + Some(name) => (true, name), + None => (false, import.name), + }; + + let mut qualified_name = format!("{module}__{import_name}"); + qualified_name = qualified_name.replace(&['/', '.', '-'] as &[char], "_"); + + let func = if let Some(import) = available_imports.get(&qualified_name) { + let call = match forward { + true => Opcode::CrossModuleForward, + false => Opcode::CrossModuleCall, + }; + let wavm = vec![ + Instruction::simple(Opcode::InitFrame), + Instruction::with_data( + call, + pack_cross_module_call(import.module, import.func), + ), + Instruction::simple(Opcode::Return), + ]; + Function::new_from_wavm(wavm, import.ty.clone(), vec![]) + } else if let Ok((hostio, debug)) = host::get_impl(import.module, import_name) { + ensure!( + (debug && debug_funcs) || (!debug && allow_hostapi), + "Host func {} in {} not enabled debug_funcs={debug_funcs} hostapi={allow_hostapi} debug={debug}", + import_name.red(), + import.module.red(), + ); + hostio } else { - bail!("Unsupport import kind {:?}", import); - } + bail!( + "No such import {} in {} for {}", + import_name.red(), + import.module.red(), + bin_name.red() + ) + }; + ensure!( + &func.ty == have_ty, + "Import {} for {} has different function signature than export.\nexpected {} in {}\nbut have {}", + import_name.red(), bin_name.red(), func.ty.red(), module.red(), have_ty.red(), + ); + + func_type_idxs.push(import.offset); + code.push(func); + host_call_hooks.push(Some((import.module.into(), import_name.into()))); } func_type_idxs.extend(bin.functions.iter()); - let internals = host::new_internal_funcs(); + let func_exports: HashMap = bin + .exports + .iter() + .filter(|(_, (_, kind))| kind == &ExportKind::Func) + .map(|(name, (offset, _))| (name.to_owned(), *offset)) + .collect(); + + let internals = host::new_internal_funcs(stylus_data); let internals_offset = (code.len() + bin.codes.len()) as u32; let internals_types = internals.iter().map(|f| f.ty.clone()); @@ -349,6 +435,7 @@ impl Module { &types, func_type_idxs[idx], internals_offset, + bin_name, ) }, func_ty.clone(), @@ -377,8 +464,8 @@ impl Module { if initial > max_size { bail!( "Memory inits to a size larger than its max: {} vs {}", - limits.initial, - max_size + limits.initial.red(), + max_size.red() ); } let size = initial * page_size; @@ -386,29 +473,12 @@ impl Module { memory = Memory::new(size as usize, max_size); } - let mut globals = vec![]; - for global in &bin.globals { - let mut init = global.init_expr.get_operators_reader(); - - let value = match (init.read()?, init.read()?, init.eof()) { - (op, Operator::End, true) => crate::binary::op_as_const(op)?, - _ => bail!("Non-constant global initializer"), - }; - globals.push(value); - } - - for export in &bin.exports { - if let ExternalKind::Func = export.kind { - exports.insert(export.name.to_owned(), export.index); - } - } - for data in &bin.datas { let (memory_index, mut init) = match data.kind { DataKind::Active { memory_index, - init_expr, - } => (memory_index, init_expr.get_operators_reader()), + offset_expr, + } => (memory_index, offset_expr.get_operators_reader()), _ => continue, }; ensure!( @@ -418,7 +488,7 @@ impl Module { let offset = match (init.read()?, init.read()?, init.eof()) { (Operator::I32Const { value }, Operator::End, true) => value as usize, - x => bail!("Non-constant element segment offset expression {:?}", x), + x => bail!("Non-constant element segment offset expression {x:?}"), }; if !matches!( offset.checked_add(data.data.len()), @@ -426,14 +496,19 @@ impl Module { ) { bail!( "Out-of-bounds data memory init with offset {} and size {}", - offset, - data.data.len(), + offset.red(), + data.data.len().red(), ); } - memory.set_range(offset, data.data); + memory.set_range(offset, data.data)?; } for table in &bin.tables { + let element_type = table.element_type; + ensure!( + element_type == RefType::FUNCREF, + "unsupported table type {element_type}" + ); tables.push(Table { elems: vec![TableElement::default(); usize::try_from(table.initial).unwrap()], ty: *table, @@ -445,36 +520,27 @@ impl Module { let (t, mut init) = match elem.kind { ElementKind::Active { table_index, - init_expr, - } => (table_index, init_expr.get_operators_reader()), - _ => continue, + offset_expr, + } => ( + table_index.unwrap_or_default() as usize, + offset_expr.get_operators_reader(), + ), + _ => continue, // we don't support the ops that use these }; let offset = match (init.read()?, init.read()?, init.eof()) { (Operator::I32Const { value }, Operator::End, true) => value as usize, - x => bail!("Non-constant element segment offset expression {:?}", x), + x => bail!("Non-constant element segment offset expression {x:?}"), }; - let table = match tables.get_mut(t as usize) { - Some(t) => t, - None => bail!("Element segment for non-exsistent table {}", t), + let Some(table) = tables.get_mut(t) else { + bail!("Element segment for non-exsistent table {}", t.red()) }; - let expected_ty = table.ty.element_type; - ensure!( - expected_ty == elem.ty, - "Element type expected to be of table type {:?} but of type {:?}", - expected_ty, - elem.ty - ); let mut contents = vec![]; - let mut item_reader = elem.items.get_items_reader()?; - for _ in 0..item_reader.get_count() { - let item = item_reader.read()?; - let index = match item { - ElementItem::Func(index) => index, - ElementItem::Expr(_) => { - bail!("Non-constant element initializers are not supported") - } - }; + let ElementItems::Functions(item_reader) = elem.items.clone() else { + bail!("Non-constant element initializers are not supported"); + }; + for func in item_reader.into_iter() { + let index = func?; let func_ty = func_types[index as usize].clone(); contents.push(TableElement { val: Value::FuncRef(index), @@ -485,19 +551,22 @@ impl Module { let len = contents.len(); ensure!( offset.saturating_add(len) <= table.elems.len(), - "Out of bounds element segment at offset {} and length {} for table of length {}", - offset, - len, + "Out of bounds element segment at offset {offset} and length {len} for table of length {}", table.elems.len(), ); table.elems[offset..][..len].clone_from_slice(&contents); } + ensure!( + code.len() < (1usize << 31), + "Module function count must be under 2^31", + ); + ensure!(!code.is_empty(), "Module has no code"); let tables_hashes: Result<_, _> = tables.iter().map(Table::hash).collect(); Ok(Module { memory, - globals, + globals: bin.globals.clone(), tables_merkle: Merkle::new(MerkleType::Table, tables_hashes?), tables, funcs_merkle: Arc::new(Merkle::new( @@ -511,11 +580,39 @@ impl Module { host_call_hooks: Arc::new(host_call_hooks), start_function: bin.start, func_types: Arc::new(func_types), - exports: Arc::new(exports), + func_exports: Arc::new(func_exports), + all_exports: Arc::new(bin.exports.clone()), + extra_hash: Arc::new(crypto::keccak(&bin.extra_data).into()), }) } - fn hash(&self) -> Bytes32 { + pub fn from_user_binary( + bin: &WasmBinary, + debug_funcs: bool, + stylus_data: Option, + ) -> Result { + Self::from_binary( + bin, + &USER_IMPORTS, + &HashMap::default(), + false, + debug_funcs, + stylus_data, + ) + } + + pub fn name(&self) -> &str { + &self.names.module + } + + fn find_func(&self, name: &str) -> Result { + let Some(func) = self.func_exports.iter().find(|x| x.0 == name) else { + bail!("func {} not found in {}", name.red(), self.name().red()) + }; + Ok(*func.1) + } + + pub fn hash(&self) -> Bytes32 { let mut h = Keccak256::new(); h.update("Module:"); h.update( @@ -528,6 +625,7 @@ impl Module { h.update(self.memory.hash()); h.update(self.tables_merkle.root()); h.update(self.funcs_merkle.root()); + h.update(*self.extra_hash); h.update(self.internals_offset.to_be_bytes()); h.finalize().into() } @@ -549,11 +647,131 @@ impl Module { data.extend(self.tables_merkle.root()); data.extend(self.funcs_merkle.root()); - + data.extend(*self.extra_hash); data.extend(self.internals_offset.to_be_bytes()); - data } + + /// Serializes the `Module` into bytes that can be stored in the db. + /// The format employed is forward-compatible with future brotli dictionary and caching policies. + pub fn into_bytes(&self) -> Vec { + let data = bincode::serialize::(&self.into()).unwrap(); + let header = vec![1 + Into::::into(Dictionary::Empty)]; + brotli::compress_into(&data, header, 0, 22, Dictionary::Empty).expect("failed to compress") + } + + /// Deserializes a `Module` from db bytes. + /// + /// # Safety + /// + /// The bytes must have been produced by `into_bytes` and represent a valid `Module`. + pub unsafe fn from_bytes(data: &[u8]) -> Self { + let module = if data[0] > 0 { + let dict = Dictionary::try_from(data[0] - 1).expect("unknown dictionary"); + let data = brotli::decompress(&data[1..], dict).expect("failed to inflate"); + bincode::deserialize::(&data) + } else { + bincode::deserialize::(&data[1..]) + }; + module.unwrap().into() + } +} + +/// This type exists to provide a serde option for serializing all the fields of a `Module`. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ModuleSerdeAll { + globals: Vec, + memory: Memory, + tables: Vec
, + tables_merkle: Merkle, + funcs: Vec, + funcs_merkle: Arc, + types: Arc>, + internals_offset: u32, + names: Arc, + host_call_hooks: Arc>>, + start_function: Option, + func_types: Arc>, + func_exports: Arc>, + all_exports: Arc, + extra_hash: Arc, +} + +impl From for Module { + fn from(module: ModuleSerdeAll) -> Self { + let funcs = module.funcs.into_iter().map(Function::from).collect(); + Self { + globals: module.globals, + memory: module.memory, + tables: module.tables, + tables_merkle: module.tables_merkle, + funcs: Arc::new(funcs), + funcs_merkle: module.funcs_merkle, + types: module.types, + internals_offset: module.internals_offset, + names: module.names, + host_call_hooks: module.host_call_hooks, + start_function: module.start_function, + func_types: module.func_types, + func_exports: module.func_exports, + all_exports: module.all_exports, + extra_hash: module.extra_hash, + } + } +} + +impl From<&Module> for ModuleSerdeAll { + fn from(module: &Module) -> Self { + let funcs = Vec::clone(&module.funcs); + Self { + globals: module.globals.clone(), + memory: module.memory.clone(), + tables: module.tables.clone(), + tables_merkle: module.tables_merkle.clone(), + funcs: funcs.into_iter().map(FunctionSerdeAll::from).collect(), + funcs_merkle: module.funcs_merkle.clone(), + types: module.types.clone(), + internals_offset: module.internals_offset, + names: module.names.clone(), + host_call_hooks: module.host_call_hooks.clone(), + start_function: module.start_function, + func_types: module.func_types.clone(), + func_exports: module.func_exports.clone(), + all_exports: module.all_exports.clone(), + extra_hash: module.extra_hash.clone(), + } + } +} + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FunctionSerdeAll { + code: Vec, + ty: FunctionType, + code_merkle: Merkle, + local_types: Vec, +} + +impl From for Function { + fn from(func: FunctionSerdeAll) -> Self { + Self { + code: func.code, + ty: func.ty, + code_merkle: func.code_merkle, + local_types: func.local_types, + } + } +} + +impl From for FunctionSerdeAll { + fn from(func: Function) -> Self { + Self { + code: func.code, + ty: func.ty, + code_merkle: func.code_merkle, + local_types: func.local_types, + } + } } // Globalstate holds: @@ -640,13 +858,39 @@ pub struct ModuleState<'a> { memory: Cow<'a, Memory>, } +/// Represents if the machine can recover and where to jump back if so. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum ThreadState { + /// Execution is in the main thread. Errors are fatal. + Main, + /// Execution is in a cothread. Errors recover to the associated pc with the main thread. + CoThread(ProgramCounter), +} + +impl ThreadState { + fn is_cothread(&self) -> bool { + match self { + ThreadState::Main => false, + ThreadState::CoThread(_) => true, + } + } + + fn serialize(&self) -> Bytes32 { + match self { + ThreadState::Main => Bytes32([0xff; 32]), + ThreadState::CoThread(pc) => (*pc).serialize(), + } + } +} + #[derive(Serialize, Deserialize)] pub struct MachineState<'a> { steps: u64, // Not part of machine hash + thread_state: ThreadState, status: MachineStatus, - value_stack: Cow<'a, Vec>, + value_stacks: Cow<'a, Vec>>, internal_stack: Cow<'a, Vec>, - frame_stack: Cow<'a, Vec>, + frame_stacks: Cow<'a, Vec>>, modules: Vec>, global_state: GlobalState, pc: ProgramCounter, @@ -678,6 +922,7 @@ impl PreimageResolverWrapper { } } + #[cfg(feature = "native")] pub fn get(&mut self, context: u64, ty: PreimageType, hash: Bytes32) -> Option<&[u8]> { // TODO: this is unnecessarily complicated by the rust borrow checker. // This will probably be simplifiable when Polonius is shipped. @@ -693,6 +938,7 @@ impl PreimageResolverWrapper { } } + #[cfg(feature = "native")] pub fn get_const(&self, context: u64, ty: PreimageType, hash: Bytes32) -> Option { if let Some(resolved) = &self.last_resolved { if resolved.0 == hash { @@ -706,10 +952,11 @@ impl PreimageResolverWrapper { #[derive(Clone, Debug)] pub struct Machine { steps: u64, // Not part of machine hash + thread_state: ThreadState, status: MachineStatus, - value_stack: Vec, + value_stacks: Vec>, internal_stack: Vec, - frame_stack: Vec, + frame_stacks: Vec>, modules: Vec, modules_merkle: Option, global_state: GlobalState, @@ -718,35 +965,79 @@ pub struct Machine { inbox_contents: HashMap<(InboxIdentifier, u64), Vec>, first_too_far: u64, // Not part of machine hash preimage_resolver: PreimageResolverWrapper, + /// Linkable Stylus modules in compressed form. Not part of the machine hash. + stylus_modules: HashMap>, initial_hash: Bytes32, context: u64, + debug_info: bool, // Not part of machine hash +} + +type FrameStackHash = Bytes32; +type ValueStackHash = Bytes32; +type MultiStackHash = Bytes32; +type InterStackHash = Bytes32; + +pub(crate) fn hash_stack(stack: I, prefix: &str) -> Bytes32 +where + I: IntoIterator, + D: AsRef<[u8]>, +{ + hash_stack_with_heights(stack, &[], prefix).0 } -fn hash_stack(stack: I, prefix: &str) -> Bytes32 +/// Hashes a stack of n elements, returning the values at various heights along the way in O(n). +fn hash_stack_with_heights( + stack: I, + mut heights: &[usize], + prefix: &str, +) -> (Bytes32, Vec) where I: IntoIterator, D: AsRef<[u8]>, { + let mut parts = vec![]; let mut hash = Bytes32::default(); + let mut count = 0; for item in stack.into_iter() { - let mut h = Keccak256::new(); - h.update(prefix); - h.update(item.as_ref()); - h.update(hash); - hash = h.finalize().into(); + while heights.first() == Some(&count) { + parts.push(hash); + heights = &heights[1..]; + } + + hash = Keccak256::new() + .chain(prefix) + .chain(item.as_ref()) + .chain(hash) + .finalize() + .into(); + + count += 1; + } + while !heights.is_empty() { + assert_eq!(heights[0], count); + parts.push(hash); + heights = &heights[1..]; } - hash + (hash, parts) } -fn hash_value_stack(stack: &[Value]) -> Bytes32 { +fn hash_value_stack(stack: &[Value]) -> ValueStackHash { hash_stack(stack.iter().map(|v| v.hash()), "Value stack:") } -fn hash_stack_frame_stack(frames: &[StackFrame]) -> Bytes32 { +fn hash_stack_frame_stack(frames: &[StackFrame]) -> FrameStackHash { hash_stack(frames.iter().map(|f| f.hash()), "Stack frame stack:") } +fn hash_multistack(multistack: &[&[T]], stack_hasher: F) -> MultiStackHash +where + F: Fn(&[T]) -> Bytes32, +{ + hash_stack(multistack.iter().map(|v| stack_hasher(v)), "cothread:") +} + #[must_use] +#[cfg(feature = "native")] fn prove_window(items: &[T], stack_hasher: F, encoder: G) -> Vec where F: Fn(&[T]) -> Bytes32, @@ -767,6 +1058,7 @@ where } #[must_use] +#[cfg(feature = "native")] fn prove_stack( items: &[T], proving_depth: usize, @@ -788,7 +1080,52 @@ where data } +// prove_multistacks encodes proof for multistacks: +// - Proof of first(main) if not cothread otherwise last +// - Hash of first if cothread, otherwise last +// - Recursive hash of the rest +// If length is < 1, hash of last element is assumed 0xff..f, same for hash +// of in-between stacks ([2nd..last)). +// Accepts prover function so that it can work both for proving stack and window. +#[must_use] +#[cfg(feature = "native")] +fn prove_multistack( + cothread: bool, + items: Vec<&[T]>, + stack_hasher: F, + multistack_hasher: MF, + prover: fn(&[T]) -> Vec, +) -> Vec +where + F: Fn(&[T]) -> Bytes32, + MF: Fn(&[&[T]], F) -> Bytes32, +{ + let mut data = Vec::with_capacity(33); + + if cothread { + data.extend(prover(items.last().unwrap())); + data.extend(stack_hasher(items.first().unwrap())) + } else { + data.extend(prover(items.first().unwrap())); + + let last_hash = if items.len() > 1 { + stack_hasher(items.last().unwrap()) + } else { + Machine::NO_STACK_HASH + }; + data.extend(last_hash); + } + let hash: Bytes32 = if items.len() > 2 { + multistack_hasher(&items[1..items.len() - 1], stack_hasher) + } else { + Bytes32::default() + }; + data.extend(hash); + data +} + #[must_use] +#[cfg(feature = "native")] fn exec_ibin_op(a: T, b: T, op: IBinOpType) -> Option where Wrapping: ReinterpretAsSigned, @@ -824,6 +1161,7 @@ where } #[must_use] +#[cfg(feature = "native")] fn exec_iun_op(a: T, op: IUnOpType) -> u32 where T: PrimInt, @@ -835,6 +1173,7 @@ where } } +#[cfg(feature = "native")] fn exec_irel_op(a: T, b: T, op: IRelOpType) -> Value where T: Ord, @@ -856,6 +1195,7 @@ pub fn get_empty_preimage_resolver() -> PreimageResolver { impl Machine { pub const MAX_STEPS: u64 = 1 << 43; + pub const NO_STACK_HASH: Bytes32 = Bytes32([255_u8; 32]); pub fn from_paths( library_paths: &[PathBuf], @@ -863,21 +1203,23 @@ impl Machine { language_support: bool, always_merkleize: bool, allow_hostapi_from_main: bool, + debug_funcs: bool, + debug_info: bool, global_state: GlobalState, inbox_contents: HashMap<(InboxIdentifier, u64), Vec>, preimage_resolver: PreimageResolver, ) -> Result { let bin_source = file_bytes(binary_path)?; - let bin = parse(&bin_source) + let bin = parse(&bin_source, binary_path) .wrap_err_with(|| format!("failed to validate WASM binary at {:?}", binary_path))?; let mut libraries = vec![]; let mut lib_sources = vec![]; for path in library_paths { let error_message = format!("failed to validate WASM binary at {:?}", path); - lib_sources.push((file_bytes(path)?, error_message)); + lib_sources.push((file_bytes(path)?, path, error_message)); } - for (source, error_message) in &lib_sources { - let library = parse(source).wrap_err_with(|| error_message.clone())?; + for (source, path, error_message) in &lib_sources { + let library = parse(source, path).wrap_err_with(|| error_message.clone())?; libraries.push(library); } Self::from_binaries( @@ -886,21 +1228,90 @@ impl Machine { language_support, always_merkleize, allow_hostapi_from_main, + debug_funcs, + debug_info, global_state, inbox_contents, preimage_resolver, + None, ) } + /// Creates an instrumented user Machine from the wasm or wat at the given `path`. + #[cfg(feature = "native")] + pub fn from_user_path(path: &Path, compile: &CompileConfig) -> Result { + let data = std::fs::read(path)?; + let wasm = wasmer::wat2wasm(&data)?; + let mut bin = binary::parse(&wasm, Path::new("user"))?; + let stylus_data = bin.instrument(compile, &Bytes32::default())?; + + let user_test = std::fs::read("../../target/machines/latest/user_test.wasm")?; + let user_test = parse(&user_test, Path::new("user_test"))?; + let wasi_stub = std::fs::read("../../target/machines/latest/wasi_stub.wasm")?; + let wasi_stub = parse(&wasi_stub, Path::new("wasi_stub"))?; + let soft_float = std::fs::read("../../target/machines/latest/soft-float.wasm")?; + let soft_float = parse(&soft_float, Path::new("soft-float"))?; + + let mut machine = Self::from_binaries( + &[soft_float, wasi_stub, user_test], + bin, + false, + false, + false, + compile.debug.debug_funcs, + true, + GlobalState::default(), + HashMap::default(), + Arc::new(|_, _, _| panic!("tried to read preimage")), + Some(stylus_data), + )?; + + let footprint: u32 = stylus_data.footprint.into(); + machine.call_function("user_test", "set_pages", vec![footprint.into()])?; + Ok(machine) + } + + /// Adds a user program to the machine's known set of wasms, compiling it into a link-able module. + /// Note that the module produced will need to be configured before execution via hostio calls. + pub fn add_program( + &mut self, + wasm: &[u8], + codehash: &Bytes32, + version: u16, + debug_funcs: bool, + ) -> Result { + let mut bin = binary::parse(wasm, Path::new("user"))?; + let config = CompileConfig::version(version, debug_funcs); + let stylus_data = bin.instrument(&config, codehash)?; + + // enable debug mode if debug funcs are available + if debug_funcs { + self.debug_info = true; + } + + let module = Module::from_user_binary(&bin, debug_funcs, Some(stylus_data))?; + let hash = module.hash(); + self.add_stylus_module(hash, module.into_bytes()); + Ok(hash) + } + + /// Adds a pre-built program to the machine's known set of wasms. + pub fn add_stylus_module(&mut self, hash: Bytes32, module: Vec) { + self.stylus_modules.insert(hash, module); + } + pub fn from_binaries( libraries: &[WasmBinary<'_>], bin: WasmBinary<'_>, runtime_support: bool, always_merkleize: bool, allow_hostapi_from_main: bool, + debug_funcs: bool, + debug_info: bool, global_state: GlobalState, inbox_contents: HashMap<(InboxIdentifier, u64), Vec>, preimage_resolver: PreimageResolver, + stylus_data: Option, ) -> Result { use ArbValueType::*; @@ -908,40 +1319,49 @@ impl Machine { let mut modules = vec![Module::default()]; let mut available_imports = HashMap::default(); let mut floating_point_impls = HashMap::default(); - - for export in &bin.exports { - if let ExternalKind::Func = export.kind { - if let Some(ty_idx) = usize::try_from(export.index) - .unwrap() - .checked_sub(bin.imports.len()) - { - let ty = bin.functions[ty_idx]; - let ty = &bin.types[usize::try_from(ty).unwrap()]; - let module = u32::try_from(modules.len() + libraries.len()).unwrap(); + let main_module_index = u32::try_from(modules.len() + libraries.len())?; + + // make the main module's exports available to libraries + for (name, &(export, kind)) in &bin.exports { + if kind == ExportKind::Func { + let index: usize = export.try_into()?; + if let Some(index) = index.checked_sub(bin.imports.len()) { + let ty: usize = bin.functions[index].try_into()?; + let ty = bin.types[ty].clone(); available_imports.insert( - format!("env__wavm_guest_call__{}", export.name), - AvailableImport { - ty: ty.clone(), - module, - func: export.index, - }, + format!("env__wavm_guest_call__{name}"), + AvailableImport::new(ty, main_module_index, export), ); } } } + // collect all the library exports in advance so they can use each other's + for (index, lib) in libraries.iter().enumerate() { + let module = 1 + index as u32; // off by one due to the entry point + for (name, &(export, kind)) in &lib.exports { + if kind == ExportKind::Func { + let ty = match lib.get_function(FunctionIndex::from_u32(export)) { + Ok(ty) => ty, + Err(error) => bail!("failed to read export {name}: {error}"), + }; + let import = AvailableImport::new(ty, module, export); + available_imports.insert(name.to_owned(), import); + } + } + } + for lib in libraries { - let module = Module::from_binary(lib, &available_imports, &floating_point_impls, true)?; - for (name, &func) in &*module.exports { + let module = Module::from_binary( + lib, + &available_imports, + &floating_point_impls, + true, + debug_funcs, + None, + )?; + for (name, &func) in &*module.func_exports { let ty = module.func_types[func as usize].clone(); - available_imports.insert( - name.clone(), - AvailableImport { - module: modules.len() as u32, - func, - ty: ty.clone(), - }, - ); if let Ok(op) = name.parse::() { let mut sig = op.signature(); // wavm codegen takes care of effecting this type change at callsites @@ -954,10 +1374,10 @@ impl Machine { } ensure!( ty == sig, - "Wrong type for floating point impl {:?} expecting {:?} but got {:?}", - name, - sig, - ty + "Wrong type for floating point impl {} expecting {} but got {}", + name.red(), + sig.red(), + ty.red() ); floating_point_impls.insert(op, (modules.len() as u32, func)); } @@ -965,13 +1385,15 @@ impl Machine { modules.push(module); } - // Shouldn't be necessary, but to safe, don't allow the main binary to import its own guest calls + // Shouldn't be necessary, but to be safe, don't allow the main binary to import its own guest calls available_imports.retain(|_, i| i.module as usize != modules.len()); modules.push(Module::from_binary( &bin, &available_imports, &floating_point_impls, allow_hostapi_from_main, + debug_funcs, + stylus_data, )?); // Build the entrypoint module @@ -1004,11 +1426,12 @@ impl Machine { } let main_module_idx = modules.len() - 1; let main_module = &modules[main_module_idx]; + let main_exports = &main_module.func_exports; // Rust support let rust_fn = "__main_void"; - if let Some(&f) = main_module.exports.get(rust_fn).filter(|_| runtime_support) { - let expected_type = FunctionType::new(vec![], vec![I32]); + if let Some(&f) = main_exports.get(rust_fn).filter(|_| runtime_support) { + let expected_type = FunctionType::new([], [I32]); ensure!( main_module.func_types[f as usize] == expected_type, "Main function doesn't match expected signature of [] -> [ret]", @@ -1018,59 +1441,15 @@ impl Machine { entry!(HaltAndSetFinished); } - // Go support - if let Some(&f) = main_module.exports.get("run").filter(|_| runtime_support) { - let mut expected_type = FunctionType::default(); - expected_type.inputs.push(I32); // argc - expected_type.inputs.push(I32); // argv + // Go/wasi support + if let Some(&f) = main_exports.get("_start").filter(|_| runtime_support) { + let expected_type = FunctionType::new([], []); ensure!( main_module.func_types[f as usize] == expected_type, - "Run function doesn't match expected signature of [argc, argv]", - ); - // Go's flags library panics if the argument list is empty. - // To pass in the program name argument, we need to put it in memory. - // The Go linker guarantees a section of memory starting at byte 4096 is available for this purpose. - // https://github.com/golang/go/blob/252324e879e32f948d885f787decf8af06f82be9/misc/wasm/wasm_exec.js#L520 - // These memory stores also assume that the Go module's memory is large enough to begin with. - // That's also handled by the Go compiler. Go 1.17.5 in the compilation of the arbitrator go test case - // initializes its memory to 272 pages long (about 18MB), much larger than the required space. - let free_memory_base = 4096; - let name_str_ptr = free_memory_base; - let argv_ptr = name_str_ptr + 8; - ensure!( - main_module.internals_offset != 0, - "Main module doesn't have internals" + "Main function doesn't match expected signature of [] -> []", ); - let main_module_idx = u32::try_from(main_module_idx).unwrap(); - let main_module_store32 = main_module.internals_offset + 3; - - // Write "js\0" to name_str_ptr, to match what the actual JS environment does - entry!(I32Const, name_str_ptr); - entry!(I32Const, 0x736a); // b"js\0" - entry!(@cross, main_module_idx, main_module_store32); - entry!(I32Const, name_str_ptr + 4); - entry!(I32Const, 0); - entry!(@cross, main_module_idx, main_module_store32); - - // Write name_str_ptr to argv_ptr - entry!(I32Const, argv_ptr); - entry!(I32Const, name_str_ptr); - entry!(@cross, main_module_idx, main_module_store32); - entry!(I32Const, argv_ptr + 4); - entry!(I32Const, 0); - entry!(@cross, main_module_idx, main_module_store32); - - // Launch main with an argument count of 1 and argv_ptr - entry!(I32Const, 1); - entry!(I32Const, argv_ptr); - entry!(@cross, main_module_idx, f); - if let Some(i) = available_imports.get("wavm__go_after_run") { - ensure!( - i.ty == FunctionType::default(), - "Resume function has non-empty function signature", - ); - entry!(@cross, i.module, i.func); - } + entry!(@cross, u32::try_from(main_module_idx).unwrap(), f); + entry!(HaltAndSetFinished); } let entrypoint_types = vec![FunctionType::default()]; @@ -1103,10 +1482,12 @@ impl Machine { types: Arc::new(entrypoint_types), names: Arc::new(entrypoint_names), internals_offset: 0, - host_call_hooks: Arc::new(Vec::new()), + host_call_hooks: Default::default(), start_function: None, func_types: Arc::new(vec![FunctionType::default()]), - exports: Arc::new(HashMap::default()), + func_exports: Default::default(), + all_exports: Default::default(), + extra_hash: Default::default(), }; modules[0] = entrypoint; @@ -1149,10 +1530,11 @@ impl Machine { let mut mach = Machine { status: MachineStatus::Running, + thread_state: ThreadState::Main, steps: 0, - value_stack: vec![Value::RefNull, Value::I32(0), Value::I32(0)], + value_stacks: vec![vec![Value::RefNull, Value::I32(0), Value::I32(0)]], internal_stack: Vec::new(), - frame_stack: Vec::new(), + frame_stacks: vec![Vec::new()], modules, modules_merkle, global_state, @@ -1161,17 +1543,24 @@ impl Machine { inbox_contents, first_too_far, preimage_resolver: PreimageResolverWrapper::new(preimage_resolver), + stylus_modules: HashMap::default(), initial_hash: Bytes32::default(), context: 0, + debug_info, }; mach.initial_hash = mach.hash(); Ok(mach) } pub fn new_from_wavm(wavm_binary: &Path) -> Result { - let f = BufReader::new(File::open(wavm_binary)?); - let decompressor = brotli2::read::BrotliDecoder::new(f); - let mut modules: Vec = bincode::deserialize_from(decompressor)?; + let mut modules: Vec = { + let compressed = std::fs::read(wavm_binary)?; + let Ok(modules) = brotli::decompress(&compressed, Dictionary::Empty) else { + bail!("failed to decompress wavm binary"); + }; + bincode::deserialize(&modules)? + }; + for module in modules.iter_mut() { for table in module.tables.iter_mut() { table.elems_merkle = Merkle::new( @@ -1182,14 +1571,9 @@ impl Machine { let tables: Result<_> = module.tables.iter().map(Table::hash).collect(); module.tables_merkle = Merkle::new(MerkleType::Table, tables?); - let funcs = - Arc::get_mut(&mut module.funcs).expect("Multiple copies of module functions"); - for func in funcs.iter_mut() { - func.code_merkle = Merkle::new( - MerkleType::Instruction, - func.code.par_iter().map(|i| i.hash()).collect(), - ); - } + let funcs = Arc::get_mut(&mut module.funcs).expect("Multiple copies of module funcs"); + funcs.iter_mut().for_each(Function::set_code_merkle); + module.funcs_merkle = Arc::new(Merkle::new( MerkleType::Function, module.funcs.iter().map(Function::hash).collect(), @@ -1197,10 +1581,11 @@ impl Machine { } let mut mach = Machine { status: MachineStatus::Running, + thread_state: ThreadState::Main, steps: 0, - value_stack: vec![Value::RefNull, Value::I32(0), Value::I32(0)], + value_stacks: vec![vec![Value::RefNull, Value::I32(0), Value::I32(0)]], internal_stack: Vec::new(), - frame_stack: Vec::new(), + frame_stacks: vec![Vec::new()], modules, modules_merkle: None, global_state: Default::default(), @@ -1209,8 +1594,10 @@ impl Machine { inbox_contents: Default::default(), first_too_far: 0, preimage_resolver: PreimageResolverWrapper::new(get_empty_preimage_resolver()), + stylus_modules: HashMap::default(), initial_hash: Bytes32::default(), context: 0, + debug_info: false, }; mach.initial_hash = mach.hash(); Ok(mach) @@ -1221,12 +1608,14 @@ impl Machine { self.hash() == self.initial_hash, "serialize_binary can only be called on initial machine", ); - let mut f = File::create(path)?; - let mut compressor = brotli2::write::BrotliEncoder::new(BufWriter::new(&mut f), 9); - bincode::serialize_into(&mut compressor, &self.modules)?; - compressor.flush()?; - drop(compressor); - f.sync_data()?; + let modules = bincode::serialize(&self.modules)?; + let window = brotli::DEFAULT_WINDOW_SIZE; + let Ok(output) = brotli::compress(&modules, 9, window, Dictionary::Empty) else { + bail!("failed to compress binary"); + }; + + let mut file = File::create(path)?; + file.write_all(&output)?; Ok(()) } @@ -1243,10 +1632,11 @@ impl Machine { .collect(); let state = MachineState { steps: self.steps, + thread_state: self.thread_state, status: self.status, - value_stack: Cow::Borrowed(&self.value_stack), + value_stacks: Cow::Borrowed(&self.value_stacks), internal_stack: Cow::Borrowed(&self.internal_stack), - frame_stack: Cow::Borrowed(&self.frame_stack), + frame_stacks: Cow::Borrowed(&self.frame_stacks), modules, global_state: self.global_state.clone(), pc: self.pc, @@ -1280,9 +1670,9 @@ impl Machine { } self.steps = new_state.steps; self.status = new_state.status; - self.value_stack = new_state.value_stack.into_owned(); + self.value_stacks = new_state.value_stacks.into_owned(); self.internal_stack = new_state.internal_stack.into_owned(); - self.frame_stack = new_state.frame_stack.into_owned(); + self.frame_stacks = new_state.frame_stacks.into_owned(); self.global_state = new_state.global_state; self.pc = new_state.pc; self.stdio_output = new_state.stdio_output.into_owned(); @@ -1306,37 +1696,154 @@ impl Machine { } } - pub fn jump_into_function(&mut self, func: &str, mut args: Vec) { + pub fn main_module_name(&self) -> String { + self.modules.last().expect("no module").name().to_owned() + } + + pub fn main_module_memory(&self) -> &Memory { + &self.modules.last().expect("no module").memory + } + + pub fn main_module_hash(&self) -> Bytes32 { + self.modules.last().expect("no module").hash() + } + + /// finds the first module with the given name + pub fn find_module(&self, name: &str) -> Result { + let Some(module) = self.modules.iter().position(|m| m.name() == name) else { + let names: Vec<_> = self.modules.iter().map(|m| m.name()).collect(); + let names = names.join(", "); + bail!("module {} not found among: {names}", name.red()) + }; + Ok(module as u32) + } + + pub fn find_module_func(&self, module: &str, func: &str) -> Result<(u32, u32)> { + let qualified = format!("{module}__{func}"); + let offset = self.find_module(module)?; + let module = &self.modules[offset as usize]; + let func = module + .find_func(func) + .or_else(|_| module.find_func(&qualified))?; + Ok((offset, func)) + } + + pub fn jump_into_func(&mut self, module: u32, func: u32, mut args: Vec) -> Result<()> { + let Some(source_module) = self.modules.get(module as usize) else { + bail!("no module at offset {}", module.red()) + }; + let Some(source_func) = source_module.funcs.get(func as usize) else { + bail!( + "no func at offset {} in module {}", + func.red(), + source_module.name().red() + ) + }; + let ty = &source_func.ty; + if ty.inputs.len() != args.len() { + let name = source_module.names.functions.get(&func).unwrap(); + bail!( + "func {} has type {} but received args {:?}", + name.red(), + ty.red(), + args.debug_red(), + ) + } + let frame_args = [Value::RefNull, Value::I32(0), Value::I32(0)]; args.extend(frame_args); - self.value_stack = args; + self.value_stacks[0] = args; - let module = self.modules.last().expect("no module"); - let export = module.exports.iter().find(|x| x.0 == func); - let export = export - .unwrap_or_else(|| panic!("func {} not found", func)) - .1; - - self.frame_stack.clear(); + self.frame_stacks[0].clear(); self.internal_stack.clear(); self.pc = ProgramCounter { - module: (self.modules.len() - 1).try_into().unwrap(), - func: *export, + module, + func, inst: 0, }; self.status = MachineStatus::Running; self.steps = 0; + Ok(()) } pub fn get_final_result(&self) -> Result> { - if !self.frame_stack.is_empty() { + if self.thread_state.is_cothread() { + bail!("machine in cothread when expecting final result") + } + if !self.frame_stacks[0].is_empty() { bail!( - "machine has not successfully computed a final result {:?}", - self.status + "machine has not successfully computed a final result {}", + self.status.red() ) } - Ok(self.value_stack.clone()) + Ok(self.value_stacks[0].clone()) + } + + #[cfg(feature = "native")] + pub fn call_function( + &mut self, + module: &str, + func: &str, + args: Vec, + ) -> Result> { + let (module, func) = self.find_module_func(module, func)?; + self.jump_into_func(module, func, args)?; + self.step_n(Machine::MAX_STEPS)?; + self.get_final_result() + } + + #[cfg(feature = "native")] + pub fn call_user_func(&mut self, func: &str, args: Vec, ink: u64) -> Result> { + self.set_ink(ink); + self.call_function("user", func, args) + } + + /// Gets the *last* global with the given name, if one exists + /// Note: two globals may have the same name, so use carefully! + pub fn get_global(&self, name: &str) -> Result { + for module in self.modules.iter().rev() { + if let Some((global, ExportKind::Global)) = module.all_exports.get(name) { + return Ok(module.globals[*global as usize]); + } + } + bail!("global {} not found", name.red()) + } + + /// Sets the *last* global with the given name, if one exists + /// Note: two globals may have the same name, so use carefully! + pub fn set_global(&mut self, name: &str, value: Value) -> Result<()> { + for module in self.modules.iter_mut().rev() { + if let Some((global, ExportKind::Global)) = module.all_exports.get(name) { + module.globals[*global as usize] = value; + return Ok(()); + } + } + bail!("global {} not found", name.red()) + } + + pub fn read_memory(&self, module: u32, ptr: u32, len: u32) -> Result<&[u8]> { + let Some(module) = &self.modules.get(module as usize) else { + bail!("no module at offset {}", module.red()) + }; + let memory = module.memory.get_range(ptr as usize, len as usize); + let error = || format!("failed memory read of {} bytes @ {}", len.red(), ptr.red()); + memory.ok_or_else(|| eyre!(error())) + } + + pub fn write_memory(&mut self, module: u32, ptr: u32, data: &[u8]) -> Result<()> { + let Some(module) = &mut self.modules.get_mut(module as usize) else { + bail!("no module at offset {}", module.red()) + }; + if let Err(err) = module.memory.set_range(ptr as usize, data) { + let msg = eyre!( + "failed to write {} bytes to memory @ {}", + data.len().red(), + ptr.red() + ); + bail!(err.wrap_err(msg)); + } + Ok(()) } pub fn get_next_instruction(&self) -> Option { @@ -1362,21 +1869,44 @@ impl Machine { Some(self.pc) } + #[cfg(feature = "native")] fn test_next_instruction(func: &Function, pc: &ProgramCounter) { - debug_assert!(func.code.len() > pc.inst.try_into().unwrap()); + let inst: usize = pc.inst.try_into().unwrap(); + debug_assert!(func.code.len() > inst); } pub fn get_steps(&self) -> u64 { self.steps } + #[cfg(feature = "native")] pub fn step_n(&mut self, n: u64) -> Result<()> { if self.is_halted() { return Ok(()); } + let (mut value_stack, mut frame_stack) = match self.thread_state { + ThreadState::Main => (&mut self.value_stacks[0], &mut self.frame_stacks[0]), + ThreadState::CoThread(_) => ( + self.value_stacks.last_mut().unwrap(), + self.frame_stacks.last_mut().unwrap(), + ), + }; let mut module = &mut self.modules[self.pc.module()]; let mut func = &module.funcs[self.pc.func()]; + macro_rules! reset_refs { + () => { + (value_stack, frame_stack) = match self.thread_state { + ThreadState::Main => (&mut self.value_stacks[0], &mut self.frame_stacks[0]), + ThreadState::CoThread(_) => ( + self.value_stacks.last_mut().unwrap(), + self.frame_stacks.last_mut().unwrap(), + ), + }; + module = &mut self.modules[self.pc.module()]; + func = &module.funcs[self.pc.func()]; + }; + } macro_rules! flush_module { () => { if let Some(merkle) = self.modules_merkle.as_mut() { @@ -1385,8 +1915,31 @@ impl Machine { }; } macro_rules! error { - () => {{ + () => { + error!("") + }; + ($format:expr $(, $message:expr)*) => {{ + flush_module!(); + + if self.debug_info { + println!("\n{} {}", "error on line".grey(), line!().pink()); + println!($format, $($message.pink()),*); + println!("{}", "backtrace:".grey()); + self.print_backtrace(true); + } + + if let ThreadState::CoThread(recovery_pc) = self.thread_state { + self.thread_state = ThreadState::Main; + self.pc = recovery_pc; + reset_refs!(); + if self.debug_info { + println!("\n{}", "switching to main thread".grey()); + println!("\n{} {:?}", "next opcode: ".grey(), func.code[self.pc.inst()]); + } + continue; + } self.status = MachineStatus::Errored; + module = &mut self.modules[self.pc.module()]; break; }}; } @@ -1394,18 +1947,23 @@ impl Machine { for _ in 0..n { self.steps += 1; if self.steps == Self::MAX_STEPS { - error!(); + println!("\n{}", "Machine out of steps".red()); + self.status = MachineStatus::Errored; + self.print_backtrace(true); + module = &mut self.modules[self.pc.module()]; + break; } + let inst = func.code[self.pc.inst()]; self.pc.inst += 1; match inst.opcode { - Opcode::Unreachable => error!(), + Opcode::Unreachable => error!("unreachable"), Opcode::Nop => {} Opcode::InitFrame => { - let caller_module_internals = self.value_stack.pop().unwrap().assume_u32(); - let caller_module = self.value_stack.pop().unwrap().assume_u32(); - let return_ref = self.value_stack.pop().unwrap(); - self.frame_stack.push(StackFrame { + let caller_module_internals = value_stack.pop().unwrap().assume_u32(); + let caller_module = value_stack.pop().unwrap().assume_u32(); + let return_ref = value_stack.pop().unwrap(); + frame_stack.push(StackFrame { return_ref, locals: func .local_types @@ -1422,15 +1980,15 @@ impl Machine { .and_then(|h| h.as_ref()) { if let Err(err) = Self::host_call_hook( - &self.value_stack, + value_stack, module, &mut self.stdio_output, &hook.0, &hook.1, ) { eprintln!( - "Failed to process host call hook for host call {:?} {:?}: {}", - hook.0, hook.1, err, + "Failed to process host call hook for host call {:?} {:?}: {err}", + hook.0, hook.1, ); } } @@ -1440,14 +1998,14 @@ impl Machine { Machine::test_next_instruction(func, &self.pc); } Opcode::ArbitraryJumpIf => { - let x = self.value_stack.pop().unwrap(); + let x = value_stack.pop().unwrap(); if !x.is_i32_zero() { self.pc.inst = inst.argument_data as u32; Machine::test_next_instruction(func, &self.pc); } } Opcode::Return => { - let frame = self.frame_stack.pop().unwrap(); + let frame = frame_stack.pop().unwrap(); match frame.return_ref { Value::RefNull => error!(), Value::InternalRef(pc) => { @@ -1465,34 +2023,56 @@ impl Machine { } } Opcode::Call => { - let current_frame = self.frame_stack.last().unwrap(); - self.value_stack.push(Value::InternalRef(self.pc)); - self.value_stack - .push(Value::I32(current_frame.caller_module)); - self.value_stack - .push(Value::I32(current_frame.caller_module_internals)); + let frame = frame_stack.last().unwrap(); + value_stack.push(Value::InternalRef(self.pc)); + value_stack.push(frame.caller_module.into()); + value_stack.push(frame.caller_module_internals.into()); self.pc.func = inst.argument_data as u32; self.pc.inst = 0; func = &module.funcs[self.pc.func()]; } Opcode::CrossModuleCall => { flush_module!(); - self.value_stack.push(Value::InternalRef(self.pc)); - self.value_stack.push(Value::I32(self.pc.module)); - self.value_stack.push(Value::I32(module.internals_offset)); + value_stack.push(Value::InternalRef(self.pc)); + value_stack.push(self.pc.module.into()); + value_stack.push(module.internals_offset.into()); let (call_module, call_func) = unpack_cross_module_call(inst.argument_data); self.pc.module = call_module; self.pc.func = call_func; self.pc.inst = 0; - module = &mut self.modules[self.pc.module()]; - func = &module.funcs[self.pc.func()]; + reset_refs!(); + } + Opcode::CrossModuleForward => { + flush_module!(); + let frame = frame_stack.last().unwrap(); + value_stack.push(Value::InternalRef(self.pc)); + value_stack.push(frame.caller_module.into()); + value_stack.push(frame.caller_module_internals.into()); + let (call_module, call_func) = unpack_cross_module_call(inst.argument_data); + self.pc.module = call_module; + self.pc.func = call_func; + self.pc.inst = 0; + reset_refs!(); + } + Opcode::CrossModuleInternalCall => { + flush_module!(); + let call_internal = inst.argument_data as u32; + let call_module = value_stack.pop().unwrap().assume_u32(); + value_stack.push(Value::InternalRef(self.pc)); + value_stack.push(self.pc.module.into()); + value_stack.push(module.internals_offset.into()); + module = &mut self.modules[call_module as usize]; + self.pc.module = call_module; + self.pc.func = module.internals_offset + call_internal; + self.pc.inst = 0; + reset_refs!(); } Opcode::CallerModuleInternalCall => { - self.value_stack.push(Value::InternalRef(self.pc)); - self.value_stack.push(Value::I32(self.pc.module)); - self.value_stack.push(Value::I32(module.internals_offset)); + value_stack.push(Value::InternalRef(self.pc)); + value_stack.push(self.pc.module.into()); + value_stack.push(module.internals_offset.into()); - let current_frame = self.frame_stack.last().unwrap(); + let current_frame = frame_stack.last().unwrap(); if current_frame.caller_module_internals > 0 { let func_idx = u32::try_from(inst.argument_data) .ok() @@ -1502,8 +2082,7 @@ impl Machine { self.pc.module = current_frame.caller_module; self.pc.func = func_idx; self.pc.inst = 0; - module = &mut self.modules[self.pc.module()]; - func = &module.funcs[self.pc.func()]; + reset_refs!(); } else { // The caller module has no internals error!(); @@ -1511,7 +2090,7 @@ impl Machine { } Opcode::CallIndirect => { let (table, ty) = crate::wavm::unpack_call_indirect(inst.argument_data); - let idx = match self.value_stack.pop() { + let idx = match value_stack.pop() { Some(Value::I32(i)) => usize::try_from(i).unwrap(), x => bail!( "WASM validation failed: top of stack before call_indirect is {:?}", @@ -1520,63 +2099,60 @@ impl Machine { }; let ty = &module.types[usize::try_from(ty).unwrap()]; let elems = &module.tables[usize::try_from(table).unwrap()].elems; - if let Some(elem) = elems.get(idx).filter(|e| &e.func_ty == ty) { - match elem.val { - Value::FuncRef(call_func) => { - let current_frame = self.frame_stack.last().unwrap(); - self.value_stack.push(Value::InternalRef(self.pc)); - self.value_stack - .push(Value::I32(current_frame.caller_module)); - self.value_stack - .push(Value::I32(current_frame.caller_module_internals)); - self.pc.func = call_func; - self.pc.inst = 0; - func = &module.funcs[self.pc.func()]; - } - Value::RefNull => error!(), - v => bail!("invalid table element value {:?}", v), + let Some(elem) = elems.get(idx).filter(|e| &e.func_ty == ty) else { + error!() + }; + match elem.val { + Value::FuncRef(call_func) => { + let frame = frame_stack.last().unwrap(); + value_stack.push(Value::InternalRef(self.pc)); + value_stack.push(frame.caller_module.into()); + value_stack.push(frame.caller_module_internals.into()); + self.pc.func = call_func; + self.pc.inst = 0; + func = &module.funcs[self.pc.func()]; } - } else { - error!(); + Value::RefNull => error!(), + v => bail!("invalid table element value {:?}", v), } } Opcode::LocalGet => { - let val = self.frame_stack.last().unwrap().locals[inst.argument_data as usize]; - self.value_stack.push(val); + let val = frame_stack.last().unwrap().locals[inst.argument_data as usize]; + value_stack.push(val); } Opcode::LocalSet => { - let val = self.value_stack.pop().unwrap(); - self.frame_stack.last_mut().unwrap().locals[inst.argument_data as usize] = val; + let val = value_stack.pop().unwrap(); + let locals = &mut frame_stack.last_mut().unwrap().locals; + if locals.len() <= inst.argument_data as usize { + error!("not enough locals") + } + locals[inst.argument_data as usize] = val; } Opcode::GlobalGet => { - self.value_stack - .push(module.globals[inst.argument_data as usize]); + value_stack.push(module.globals[inst.argument_data as usize]); } Opcode::GlobalSet => { - let val = self.value_stack.pop().unwrap(); + let val = value_stack.pop().unwrap(); module.globals[inst.argument_data as usize] = val; } Opcode::MemoryLoad { ty, bytes, signed } => { - let base = match self.value_stack.pop() { + let base = match value_stack.pop() { Some(Value::I32(x)) => x, x => bail!( "WASM validation failed: top of stack before memory load is {:?}", x, ), }; - if let Some(idx) = inst.argument_data.checked_add(base.into()) { - let val = module.memory.get_value(idx, ty, bytes, signed); - if let Some(val) = val { - self.value_stack.push(val); - } else { - error!(); - } - } else { - error!(); - } + let Some(index) = inst.argument_data.checked_add(base.into()) else { + error!() + }; + let Some(value) = module.memory.get_value(index, ty, bytes, signed) else { + error!("failed to read offset {}", index) + }; + value_stack.push(value); } Opcode::MemoryStore { ty: _, bytes } => { - let val = match self.value_stack.pop() { + let val = match value_stack.pop() { Some(Value::I32(x)) => x.into(), Some(Value::I64(x)) => x, Some(Value::F32(x)) => x.to_bits().into(), @@ -1586,53 +2162,50 @@ impl Machine { x, ), }; - let base = match self.value_stack.pop() { + let base = match value_stack.pop() { Some(Value::I32(x)) => x, x => bail!( "WASM validation failed: attempted to memory store with index type {:?}", x, ), }; - if let Some(idx) = inst.argument_data.checked_add(base.into()) { - if !module.memory.store_value(idx, val, bytes) { - error!(); - } - } else { + let Some(idx) = inst.argument_data.checked_add(base.into()) else { + error!() + }; + if !module.memory.store_value(idx, val, bytes) { error!(); } } Opcode::I32Const => { - self.value_stack.push(Value::I32(inst.argument_data as u32)); + value_stack.push(Value::I32(inst.argument_data as u32)); } Opcode::I64Const => { - self.value_stack.push(Value::I64(inst.argument_data)); + value_stack.push(Value::I64(inst.argument_data)); } Opcode::F32Const => { - self.value_stack - .push(Value::F32(f32::from_bits(inst.argument_data as u32))); + value_stack.push(f32::from_bits(inst.argument_data as u32).into()); } Opcode::F64Const => { - self.value_stack - .push(Value::F64(f64::from_bits(inst.argument_data))); + value_stack.push(f64::from_bits(inst.argument_data).into()); } Opcode::I32Eqz => { - let val = self.value_stack.pop().unwrap(); - self.value_stack.push(Value::I32(val.is_i32_zero() as u32)); + let val = value_stack.pop().unwrap(); + value_stack.push(Value::I32(val.is_i32_zero() as u32)); } Opcode::I64Eqz => { - let val = self.value_stack.pop().unwrap(); - self.value_stack.push(Value::I32(val.is_i64_zero() as u32)); + let val = value_stack.pop().unwrap(); + value_stack.push(Value::I32(val.is_i64_zero() as u32)); } Opcode::IRelOp(t, op, signed) => { - let vb = self.value_stack.pop(); - let va = self.value_stack.pop(); + let vb = value_stack.pop(); + let va = value_stack.pop(); match t { IntegerValType::I32 => { if let (Some(Value::I32(a)), Some(Value::I32(b))) = (va, vb) { if signed { - self.value_stack.push(exec_irel_op(a as i32, b as i32, op)); + value_stack.push(exec_irel_op(a as i32, b as i32, op)); } else { - self.value_stack.push(exec_irel_op(a, b, op)); + value_stack.push(exec_irel_op(a, b, op)); } } else { bail!("WASM validation failed: wrong types for i32relop"); @@ -1641,9 +2214,9 @@ impl Machine { IntegerValType::I64 => { if let (Some(Value::I64(a)), Some(Value::I64(b))) = (va, vb) { if signed { - self.value_stack.push(exec_irel_op(a as i64, b as i64, op)); + value_stack.push(exec_irel_op(a as i64, b as i64, op)); } else { - self.value_stack.push(exec_irel_op(a, b, op)); + value_stack.push(exec_irel_op(a, b, op)); } } else { bail!("WASM validation failed: wrong types for i64relop"); @@ -1652,26 +2225,26 @@ impl Machine { } } Opcode::Drop => { - self.value_stack.pop().unwrap(); + value_stack.pop().unwrap(); } Opcode::Select => { - let selector_zero = self.value_stack.pop().unwrap().is_i32_zero(); - let val2 = self.value_stack.pop().unwrap(); - let val1 = self.value_stack.pop().unwrap(); + let selector_zero = value_stack.pop().unwrap().is_i32_zero(); + let val2 = value_stack.pop().unwrap(); + let val1 = value_stack.pop().unwrap(); if selector_zero { - self.value_stack.push(val2); + value_stack.push(val2); } else { - self.value_stack.push(val1); + value_stack.push(val1); } } Opcode::MemorySize => { let pages = u32::try_from(module.memory.size() / Memory::PAGE_SIZE) .expect("Memory pages grew past a u32"); - self.value_stack.push(Value::I32(pages)); + value_stack.push(pages.into()); } Opcode::MemoryGrow => { let old_size = module.memory.size(); - let adding_pages = match self.value_stack.pop() { + let adding_pages = match value_stack.pop() { Some(Value::I32(x)) => x, v => bail!("WASM validation failed: bad value for memory.grow {:?}", v), }; @@ -1691,142 +2264,132 @@ impl Machine { module.memory.resize(usize::try_from(new_size).unwrap()); // Push the old number of pages let old_pages = u32::try_from(old_size / page_size).unwrap(); - self.value_stack.push(Value::I32(old_pages)); + value_stack.push(old_pages.into()); } else { // Push -1 - self.value_stack.push(Value::I32(u32::MAX)); + value_stack.push(u32::MAX.into()); } } Opcode::IUnOp(w, op) => { - let va = self.value_stack.pop(); + let va = value_stack.pop(); match w { IntegerValType::I32 => { - if let Some(Value::I32(a)) = va { - self.value_stack.push(Value::I32(exec_iun_op(a, op))); - } else { + let Some(Value::I32(value)) = va else { bail!("WASM validation failed: wrong types for i32unop"); - } + }; + value_stack.push(exec_iun_op(value, op).into()); } IntegerValType::I64 => { - if let Some(Value::I64(a)) = va { - self.value_stack.push(Value::I64(exec_iun_op(a, op) as u64)); - } else { + let Some(Value::I64(value)) = va else { bail!("WASM validation failed: wrong types for i64unop"); - } + }; + value_stack.push(Value::I64(exec_iun_op(value, op) as u64)); } } } Opcode::IBinOp(w, op) => { - let vb = self.value_stack.pop(); - let va = self.value_stack.pop(); + let vb = value_stack.pop(); + let va = value_stack.pop(); match w { IntegerValType::I32 => { - if let (Some(Value::I32(a)), Some(Value::I32(b))) = (va, vb) { - if op == IBinOpType::DivS - && (a as i32) == i32::MIN - && (b as i32) == -1 - { - error!(); - } - let value = match exec_ibin_op(a, b, op) { - Some(value) => value, - None => error!(), - }; - self.value_stack.push(Value::I32(value)) - } else { - bail!("WASM validation failed: wrong types for i32binop"); + let (Some(Value::I32(a)), Some(Value::I32(b))) = (va, vb) else { + bail!("WASM validation failed: wrong types for i32binop") + }; + if op == IBinOpType::DivS && (a as i32) == i32::MIN && (b as i32) == -1 + { + error!() } + let Some(value) = exec_ibin_op(a, b, op) else { + error!() + }; + value_stack.push(value.into()); } IntegerValType::I64 => { - if let (Some(Value::I64(a)), Some(Value::I64(b))) = (va, vb) { - if op == IBinOpType::DivS - && (a as i64) == i64::MIN - && (b as i64) == -1 - { - error!(); - } - let value = match exec_ibin_op(a, b, op) { - Some(value) => value, - None => error!(), - }; - self.value_stack.push(Value::I64(value)) - } else { - bail!("WASM validation failed: wrong types for i64binop"); + let (Some(Value::I64(a)), Some(Value::I64(b))) = (va, vb) else { + bail!("WASM validation failed: wrong types for i64binop") + }; + if op == IBinOpType::DivS && (a as i64) == i64::MIN && (b as i64) == -1 + { + error!(); } + let Some(value) = exec_ibin_op(a, b, op) else { + error!() + }; + value_stack.push(value.into()); } } } Opcode::I32WrapI64 => { - let x = match self.value_stack.pop() { + let x = match value_stack.pop() { Some(Value::I64(x)) => x, v => bail!( "WASM validation failed: wrong type for i32.wrapi64: {:?}", v, ), }; - self.value_stack.push(Value::I32(x as u32)); + value_stack.push(Value::I32(x as u32)); } Opcode::I64ExtendI32(signed) => { - let x: u32 = self.value_stack.pop().unwrap().assume_u32(); + let x: u32 = value_stack.pop().unwrap().assume_u32(); let x64 = match signed { true => x as i32 as i64 as u64, false => x as u64, }; - self.value_stack.push(Value::I64(x64)); + value_stack.push(x64.into()); } Opcode::Reinterpret(dest, source) => { - let val = match self.value_stack.pop() { + let val = match value_stack.pop() { Some(Value::I32(x)) if source == ArbValueType::I32 => { assert_eq!(dest, ArbValueType::F32, "Unsupported reinterpret"); - Value::F32(f32::from_bits(x)) + f32::from_bits(x).into() } Some(Value::I64(x)) if source == ArbValueType::I64 => { assert_eq!(dest, ArbValueType::F64, "Unsupported reinterpret"); - Value::F64(f64::from_bits(x)) + f64::from_bits(x).into() } Some(Value::F32(x)) if source == ArbValueType::F32 => { assert_eq!(dest, ArbValueType::I32, "Unsupported reinterpret"); - Value::I32(x.to_bits()) + x.to_bits().into() } Some(Value::F64(x)) if source == ArbValueType::F64 => { assert_eq!(dest, ArbValueType::I64, "Unsupported reinterpret"); - Value::I64(x.to_bits()) + x.to_bits().into() } v => bail!("bad reinterpret: val {:?} source {:?}", v, source), }; - self.value_stack.push(val); + value_stack.push(val); } Opcode::I32ExtendS(b) => { - let mut x = self.value_stack.pop().unwrap().assume_u32(); + let mut x = value_stack.pop().unwrap().assume_u32(); let mask = (1u32 << b) - 1; x &= mask; if x & (1 << (b - 1)) != 0 { x |= !mask; } - self.value_stack.push(Value::I32(x)); + value_stack.push(x.into()); } Opcode::I64ExtendS(b) => { - let mut x = self.value_stack.pop().unwrap().assume_u64(); + let mut x = value_stack.pop().unwrap().assume_u64(); let mask = (1u64 << b) - 1; x &= mask; if x & (1 << (b - 1)) != 0 { x |= !mask; } - self.value_stack.push(Value::I64(x)); + value_stack.push(x.into()); } Opcode::MoveFromStackToInternal => { - self.internal_stack.push(self.value_stack.pop().unwrap()); + self.internal_stack.push(value_stack.pop().unwrap()); } Opcode::MoveFromInternalToStack => { - self.value_stack.push(self.internal_stack.pop().unwrap()); + value_stack.push(self.internal_stack.pop().unwrap()); } Opcode::Dup => { - let val = self.value_stack.last().cloned().unwrap(); - self.value_stack.push(val); + let val = value_stack.last().cloned().unwrap(); + value_stack.push(val); } Opcode::GetGlobalStateBytes32 => { - let ptr = self.value_stack.pop().unwrap().assume_u32(); - let idx = self.value_stack.pop().unwrap().assume_u32() as usize; + let ptr = value_stack.pop().unwrap().assume_u32(); + let idx = value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.bytes32_vals.len() || !module .memory @@ -1836,8 +2399,8 @@ impl Machine { } } Opcode::SetGlobalStateBytes32 => { - let ptr = self.value_stack.pop().unwrap().assume_u32(); - let idx = self.value_stack.pop().unwrap().assume_u32() as usize; + let ptr = value_stack.pop().unwrap().assume_u32(); + let idx = value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.bytes32_vals.len() { error!(); } else if let Some(hash) = module.memory.load_32_byte_aligned(ptr.into()) { @@ -1847,17 +2410,16 @@ impl Machine { } } Opcode::GetGlobalStateU64 => { - let idx = self.value_stack.pop().unwrap().assume_u32() as usize; + let idx = value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.u64_vals.len() { error!(); } else { - self.value_stack - .push(Value::I64(self.global_state.u64_vals[idx])); + value_stack.push(self.global_state.u64_vals[idx].into()); } } Opcode::SetGlobalStateU64 => { - let val = self.value_stack.pop().unwrap().assume_u64(); - let idx = self.value_stack.pop().unwrap().assume_u32() as usize; + let val = value_stack.pop().unwrap().assume_u64(); + let idx = value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.u64_vals.len() { error!(); } else { @@ -1865,59 +2427,61 @@ impl Machine { } } Opcode::ReadPreImage => { - let offset = self.value_stack.pop().unwrap().assume_u32(); - let ptr = self.value_stack.pop().unwrap().assume_u32(); + let offset = value_stack.pop().unwrap().assume_u32(); + let ptr = value_stack.pop().unwrap().assume_u32(); let preimage_ty = PreimageType::try_from(u8::try_from(inst.argument_data)?)?; // Preimage reads must be word aligned if offset % 32 != 0 { error!(); } - if let Some(hash) = module.memory.load_32_byte_aligned(ptr.into()) { - if let Some(preimage) = - self.preimage_resolver.get(self.context, preimage_ty, hash) - { - if preimage_ty == PreimageType::EthVersionedHash - && preimage.len() != BYTES_PER_BLOB - { - bail!( - "kzg hash {} preimage should be {} bytes long but is instead {}", - hash, - BYTES_PER_BLOB, - preimage.len(), - ); - } - if preimage_ty == PreimageType::EigenDAHash { - if !preimage.len().is_power_of_two() { - bail!("EigenDA hash preimage length should be a power of two but is instead {}", preimage.len()); - } + let Some(hash) = module.memory.load_32_byte_aligned(ptr.into()) else { + error!(); + }; - println!("EIGENDA HASH PREIMAGE: {:?}", preimage); - } + let Some(preimage) = + self.preimage_resolver.get(self.context, preimage_ty, hash) + else { + eprintln!( + "{} for hash {}", + "Missing requested preimage".red(), + hash.red(), + ); + self.print_backtrace(true); + bail!("missing requested preimage for hash {}", hash); + }; + + if preimage_ty == PreimageType::EthVersionedHash + && preimage.len() != BYTES_PER_BLOB + { + bail!( + "kzg hash {} preimage should be {} bytes long but is instead {}", + hash, + BYTES_PER_BLOB, + preimage.len(), + ); + } + + if preimage_ty == PreimageType::EigenDAHash { + if !preimage.len().is_power_of_two() { + bail!("EigenDA hash preimage length should be a power of two but is instead {}", preimage.len()); + } + println!("EIGENDA HASH PREIMAGE: {:?}", preimage); + } + let offset = usize::try_from(offset).unwrap(); let len = std::cmp::min(32, preimage.len().saturating_sub(offset)); let read = preimage.get(offset..(offset + len)).unwrap_or_default(); let success = module.memory.store_slice_aligned(ptr.into(), read); assert!(success, "Failed to write to previously read memory"); - self.value_stack.push(Value::I32(len as u32)); - } else { - eprintln!( - "{} for hash {}", - "Missing requested preimage".red(), - hash.red(), - ); - // self.eprint_backtrace(); - // bail!("missing requested preimage for hash {}", hash); - } - } else { - error!(); - } + value_stack.push(Value::I32(len as u32)); + } Opcode::ReadInboxMessage => { - let offset = self.value_stack.pop().unwrap().assume_u32(); - let ptr = self.value_stack.pop().unwrap().assume_u32(); - let msg_num = self.value_stack.pop().unwrap().assume_u64(); + let offset = value_stack.pop().unwrap().assume_u32(); + let ptr = value_stack.pop().unwrap().assume_u32(); + let msg_num = value_stack.pop().unwrap().assume_u64(); let inbox_identifier = argument_data_to_inbox(inst.argument_data).expect("Bad inbox indentifier"); if let Some(message) = self.inbox_contents.get(&(inbox_identifier, msg_num)) { @@ -1928,7 +2492,7 @@ impl Machine { let len = std::cmp::min(32, message.len().saturating_sub(offset)); let read = message.get(offset..(offset + len)).unwrap_or_default(); if module.memory.store_slice_aligned(ptr.into(), read) { - self.value_stack.push(Value::I32(len as u32)); + value_stack.push(Value::I32(len as u32)); } else { error!(); } @@ -1937,7 +2501,7 @@ impl Machine { let delayed = inbox_identifier == InboxIdentifier::Delayed; if msg_num < self.first_too_far || delayed { eprintln!("{} {msg_num}", "Missing inbox message".red()); - self.eprint_backtrace(); + self.print_backtrace(true); bail!( "missing inbox message {msg_num} of {}", self.first_too_far - 1 @@ -1947,25 +2511,80 @@ impl Machine { break; } } + Opcode::LinkModule => { + let ptr = value_stack.pop().unwrap().assume_u32(); + let Some(hash) = module.memory.load_32_byte_aligned(ptr.into()) else { + error!("no hash for {}", ptr) + }; + let Some(bytes) = self.stylus_modules.get(&hash) else { + let modules = &self.stylus_modules; + let keys: Vec<_> = modules.keys().take(16).map(hex::encode).collect(); + let dots = (modules.len() > 16).then_some("...").unwrap_or_default(); + bail!("no program for {hash} in {{{}{dots}}}", keys.join(", ")) + }; + flush_module!(); + + // put the new module's offset on the stack + let index = self.modules.len() as u32; + value_stack.push(index.into()); + + self.modules.push(unsafe { Module::from_bytes(bytes) }); + if let Some(cached) = &mut self.modules_merkle { + cached.push_leaf(hash); + } + reset_refs!(); + } + Opcode::UnlinkModule => { + flush_module!(); + self.modules.pop(); + if let Some(cached) = &mut self.modules_merkle { + cached.pop_leaf(); + } + reset_refs!(); + } Opcode::HaltAndSetFinished => { self.status = MachineStatus::Finished; break; } + Opcode::NewCoThread => { + if self.thread_state.is_cothread() { + error!("called NewCoThread from cothread") + } + self.value_stacks.push(Vec::new()); + self.frame_stacks.push(Vec::new()); + reset_refs!(); + } + Opcode::PopCoThread => { + if self.thread_state.is_cothread() { + error!("called PopCoThread from cothread") + } + self.value_stacks.pop(); + self.frame_stacks.pop(); + reset_refs!(); + } + Opcode::SwitchThread => { + let next_recovery = match inst.argument_data { + 0 => ThreadState::Main, + x => ThreadState::CoThread(self.pc.add((x - 1).try_into().unwrap())), + }; + if next_recovery.is_cothread() == self.thread_state.is_cothread() { + error!("SwitchThread doesn't switch") + } + self.thread_state = next_recovery; + reset_refs!(); + } } } flush_module!(); if self.is_halted() && !self.stdio_output.is_empty() { // If we halted, print out any trailing output that didn't have a newline. - println!( - "{} {}", - "WASM says:".yellow(), - String::from_utf8_lossy(&self.stdio_output), - ); + Self::say(String::from_utf8_lossy(&self.stdio_output)); self.stdio_output.clear(); } Ok(()) } + #[cfg(feature = "native")] fn host_call_hook( value_stack: &[Value], module: &Module, @@ -2029,10 +2648,7 @@ impl Machine { stdio_output.extend_from_slice(read_bytes_segment!(data_ptr, data_size)); } while let Some(mut idx) = stdio_output.iter().position(|&c| c == b'\n') { - println!( - "\x1b[33mWASM says:\x1b[0m {}", - String::from_utf8_lossy(&stdio_output[..idx]), - ); + Self::say(String::from_utf8_lossy(&stdio_output[..idx])); if stdio_output.get(idx + 1) == Some(&b'\r') { idx += 1; } @@ -2040,10 +2656,40 @@ impl Machine { } Ok(()) } + ("console", "log_i32" | "log_i64" | "log_f32" | "log_f64") + | ("console", "tee_i32" | "tee_i64" | "tee_f32" | "tee_f64") => { + let value = value_stack.last().ok_or_else(|| eyre!("missing value"))?; + Self::say(value); + Ok(()) + } + ("console", "log_txt") => { + let ptr = pull_arg!(1, I32); + let len = pull_arg!(0, I32); + let text = read_bytes_segment!(ptr, len); + match std::str::from_utf8(text) { + Ok(text) => Self::say(text), + Err(_) => Self::say(hex::encode(text)), + } + Ok(()) + } _ => Ok(()), } } + pub fn say(text: D) { + println!("{} {text}", "WASM says:".yellow()); + } + + pub fn print_modules(&self) { + for module in &self.modules { + println!("{module}\n"); + } + for module in self.stylus_modules.values() { + let module = unsafe { Module::from_bytes(module) }; + println!("{module}\n"); + } + } + pub fn is_halted(&self) -> bool { self.status != MachineStatus::Running } @@ -2067,18 +2713,87 @@ impl Machine { self.get_modules_merkle().root() } + fn stack_hashes(&self) -> (FrameStackHash, ValueStackHash, InterStackHash) { + macro_rules! compute { + ($stack:expr, $prefix:expr) => {{ + let frames = $stack.iter().map(|v| v.hash()); + hash_stack(frames, concat!($prefix, " stack:")) + }}; + } + // compute_multistack returns the hash of multistacks as follows: + // Keccak( + // "multistack:" + // + hash_stack(first_stack) + // + hash_stack(last_stack) + // + Keccak("cothread:" + 2nd_stack+Keccak("cothread:" + 3drd_stack + ...) + // ) + macro_rules! compute_multistack { + ($field:expr, $stacks:expr, $prefix:expr, $hasher: expr) => {{ + let first_elem = *$stacks.first().unwrap(); + let first_hash = hash_stack( + first_elem.iter().map(|v| v.hash()), + concat!($prefix, " stack:"), + ); + + let last_hash = if $stacks.len() <= 1 { + Machine::NO_STACK_HASH + } else { + let last_elem = *$stacks.last().unwrap(); + hash_stack( + last_elem.iter().map(|v| v.hash()), + concat!($prefix, " stack:"), + ) + }; + + // Hash of stacks [2nd..last) or 0xfff...f if len <= 2. + let mut hash = if $stacks.len() <= 2 { + Bytes32::default() + } else { + hash_multistack(&$stacks[1..$stacks.len() - 1], $hasher) + }; + + hash = Keccak256::new() + .chain("multistack:") + .chain(first_hash) + .chain(last_hash) + .chain(hash) + .finalize() + .into(); + hash + }}; + } + let frame_stacks = compute_multistack!( + |x| x.frame_stack, + self.get_frame_stacks(), + "Stack frame", + hash_stack_frame_stack + ); + let value_stacks = compute_multistack!( + |x| x.value_stack, + self.get_data_stacks(), + "Value", + hash_value_stack + ); + let inter_stack = compute!(self.internal_stack, "Value"); + + (frame_stacks, value_stacks, inter_stack) + } + pub fn hash(&self) -> Bytes32 { let mut h = Keccak256::new(); match self.status { MachineStatus::Running => { + let (frame_stacks, value_stacks, inter_stack) = self.stack_hashes(); + h.update(b"Machine running:"); - h.update(hash_value_stack(&self.value_stack)); - h.update(hash_value_stack(&self.internal_stack)); - h.update(hash_stack_frame_stack(&self.frame_stack)); + h.update(value_stacks); + h.update(inter_stack); + h.update(frame_stacks); h.update(self.global_state.hash()); h.update(self.pc.module.to_be_bytes()); h.update(self.pc.func.to_be_bytes()); h.update(self.pc.inst.to_be_bytes()); + h.update(self.thread_state.serialize()); h.update(self.get_modules_root()); } MachineStatus::Finished => { @@ -2095,53 +2810,74 @@ impl Machine { h.finalize().into() } + #[cfg(feature = "native")] pub fn serialize_proof(&self) -> Vec { // Could be variable, but not worth it yet const STACK_PROVING_DEPTH: usize = 3; let mut data = vec![self.status as u8]; - data.extend(prove_stack( - &self.value_stack, - STACK_PROVING_DEPTH, + macro_rules! out { + ($bytes:expr) => { + data.extend($bytes); + }; + } + macro_rules! fail { + ($format:expr $(,$message:expr)*) => {{ + let text = format!($format, $($message.red()),*); + panic!("WASM validation failed: {text}"); + }}; + } + out!(prove_multistack( + self.thread_state.is_cothread(), + self.get_data_stacks(), hash_value_stack, - |v| v.serialize_for_proof(), + hash_multistack, + |stack| prove_stack(stack, STACK_PROVING_DEPTH, hash_value_stack, |v| v + .serialize_for_proof()), )); - data.extend(prove_stack( + out!(prove_stack( &self.internal_stack, 1, hash_value_stack, |v| v.serialize_for_proof(), )); - data.extend(prove_window( - &self.frame_stack, + out!(prove_multistack( + self.thread_state.is_cothread(), + self.get_frame_stacks(), hash_stack_frame_stack, - StackFrame::serialize_for_proof, + hash_multistack, + |stack| prove_window( + stack, + hash_stack_frame_stack, + StackFrame::serialize_for_proof + ), )); - data.extend(self.global_state.hash()); + out!(self.global_state.hash()); + + out!(self.pc.module.to_be_bytes()); + out!(self.pc.func.to_be_bytes()); + out!(self.pc.inst.to_be_bytes()); + + out!(self.thread_state.serialize()); - data.extend(self.pc.module.to_be_bytes()); - data.extend(self.pc.func.to_be_bytes()); - data.extend(self.pc.inst.to_be_bytes()); let mod_merkle = self.get_modules_merkle(); - data.extend(mod_merkle.root()); + out!(mod_merkle.root()); // End machine serialization, serialize module let module = &self.modules[self.pc.module()]; let mem_merkle = module.memory.merkelize(); - data.extend(module.serialize_for_proof(&mem_merkle)); + out!(module.serialize_for_proof(&mem_merkle)); // Prove module is in modules merkle tree - data.extend( - mod_merkle - .prove(self.pc.module()) - .expect("Failed to prove module"), - ); + out!(mod_merkle + .prove(self.pc.module()) + .expect("Failed to prove module")); if self.is_halted() { return data; @@ -2150,59 +2886,49 @@ impl Machine { // Begin next instruction proof let func = &module.funcs[self.pc.func()]; - data.extend(func.code[self.pc.inst()].serialize_for_proof()); - data.extend( - func.code_merkle - .prove(self.pc.inst()) - .expect("Failed to prove against code merkle"), - ); - data.extend( - module - .funcs_merkle - .prove(self.pc.func()) - .expect("Failed to prove against function merkle"), - ); + out!(func.serialize_body_for_proof(self.pc)); + out!(func + .code_merkle + .prove(self.pc.inst() / Function::CHUNK_SIZE) + .expect("Failed to prove against code merkle")); + out!(module + .funcs_merkle + .prove(self.pc.func()) + .expect("Failed to prove against function merkle")); // End next instruction proof, begin instruction specific serialization - if let Some(next_inst) = func.code.get(self.pc.inst()) { - if matches!( - next_inst.opcode, - Opcode::GetGlobalStateBytes32 - | Opcode::SetGlobalStateBytes32 - | Opcode::GetGlobalStateU64 - | Opcode::SetGlobalStateU64 - ) { - data.extend(self.global_state.serialize()); + let Some(next_inst) = func.code.get(self.pc.inst()) else { + return data; + }; + + let op = next_inst.opcode; + let arg = next_inst.argument_data; + let value_stack = self.get_data_stack(); + let frame_stack = self.get_frame_stack(); + + use Opcode::*; + match op { + GetGlobalStateU64 | SetGlobalStateU64 => { + out!(self.global_state.serialize()); } - if matches!(next_inst.opcode, Opcode::LocalGet | Opcode::LocalSet) { - let locals = &self.frame_stack.last().unwrap().locals; - let idx = next_inst.argument_data as usize; - data.extend(locals[idx].serialize_for_proof()); - let locals_merkle = + LocalGet | LocalSet => { + let locals = &frame_stack.last().unwrap().locals; + let idx = arg as usize; + out!(locals[idx].serialize_for_proof()); + let merkle = Merkle::new(MerkleType::Value, locals.iter().map(|v| v.hash()).collect()); - data.extend( - locals_merkle - .prove(idx) - .expect("Out of bounds local access"), - ); - } else if matches!(next_inst.opcode, Opcode::GlobalGet | Opcode::GlobalSet) { - let idx = next_inst.argument_data as usize; - data.extend(module.globals[idx].serialize_for_proof()); - let locals_merkle = Merkle::new( - MerkleType::Value, - module.globals.iter().map(|v| v.hash()).collect(), - ); - data.extend( - locals_merkle - .prove(idx) - .expect("Out of bounds global access"), - ); - } else if matches!( - next_inst.opcode, - Opcode::MemoryLoad { .. } | Opcode::MemoryStore { .. }, - ) { - let is_store = matches!(next_inst.opcode, Opcode::MemoryStore { .. }); + out!(merkle.prove(idx).expect("Out of bounds local access")); + } + GlobalGet | GlobalSet => { + let idx = arg as usize; + out!(module.globals[idx].serialize_for_proof()); + let globals_merkle = module.globals.iter().map(|v| v.hash()).collect(); + let merkle = Merkle::new(MerkleType::Value, globals_merkle); + out!(merkle.prove(idx).expect("Out of bounds global access")); + } + MemoryLoad { .. } | MemoryStore { .. } => { + let is_store = matches!(op, MemoryStore { .. }); // this isn't really a bool -> int, it's determining an offset based on a bool #[allow(clippy::bool_to_int_with_if)] let stack_idx_offset = if is_store { @@ -2211,24 +2937,21 @@ impl Machine { } else { 0 }; - let base = match self - .value_stack - .get(self.value_stack.len() - 1 - stack_idx_offset) - { + let base = match value_stack.get(value_stack.len() - 1 - stack_idx_offset) { Some(Value::I32(x)) => *x, - x => panic!("WASM validation failed: memory index type is {:?}", x), + x => fail!("memory index type is {x:?}"), }; if let Some(mut idx) = u64::from(base) - .checked_add(next_inst.argument_data) + .checked_add(arg) .and_then(|x| usize::try_from(x).ok()) { // Prove the leaf this index is in, and the next one, if they are within the memory's size. idx /= Memory::LEAF_SIZE; - data.extend(module.memory.get_leaf_data(idx)); - data.extend(mem_merkle.prove(idx).unwrap_or_default()); + out!(module.memory.get_leaf_data(idx)); + out!(mem_merkle.prove(idx).unwrap_or_default()); // Now prove the next leaf too, in case it's accessed. let next_leaf_idx = idx.saturating_add(1); - data.extend(module.memory.get_leaf_data(next_leaf_idx)); + out!(module.memory.get_leaf_data(next_leaf_idx)); let second_mem_merkle = if is_store { // For stores, prove the second merkle against a state after the first leaf is set. // This state also happens to have the second leaf set, but that's irrelevant. @@ -2242,86 +2965,77 @@ impl Machine { } else { mem_merkle.into_owned() }; - data.extend(second_mem_merkle.prove(next_leaf_idx).unwrap_or_default()); + out!(second_mem_merkle.prove(next_leaf_idx).unwrap_or_default()); } - } else if next_inst.opcode == Opcode::CallIndirect { - let (table, ty) = crate::wavm::unpack_call_indirect(next_inst.argument_data); - let idx = match self.value_stack.last() { + } + CallIndirect => { + let (table, ty) = crate::wavm::unpack_call_indirect(arg); + let idx = match value_stack.last() { Some(Value::I32(i)) => *i, - x => panic!( - "WASM validation failed: top of stack before call_indirect is {:?}", - x, - ), + x => fail!("top of stack before call_indirect is {x:?}"), }; let ty = &module.types[usize::try_from(ty).unwrap()]; - data.extend((table as u64).to_be_bytes()); - data.extend(ty.hash()); + out!((table as u64).to_be_bytes()); + out!(ty.hash()); let table_usize = usize::try_from(table).unwrap(); let table = &module.tables[table_usize]; - data.extend( - table - .serialize_for_proof() - .expect("failed to serialize table"), - ); - data.extend( - module - .tables_merkle - .prove(table_usize) - .expect("Failed to prove tables merkle"), - ); + out!(table + .serialize_for_proof() + .expect("failed to serialize table")); + out!(module + .tables_merkle + .prove(table_usize) + .expect("Failed to prove tables merkle")); let idx_usize = usize::try_from(idx).unwrap(); if let Some(elem) = table.elems.get(idx_usize) { - data.extend(elem.func_ty.hash()); - data.extend(elem.val.serialize_for_proof()); - data.extend( - table - .elems_merkle - .prove(idx_usize) - .expect("Failed to prove elements merkle"), - ); + out!(elem.func_ty.hash()); + out!(elem.val.serialize_for_proof()); + out!(table + .elems_merkle + .prove(idx_usize) + .expect("Failed to prove elements merkle")); } - } else if matches!( - next_inst.opcode, - Opcode::GetGlobalStateBytes32 | Opcode::SetGlobalStateBytes32, - ) { - let ptr = self.value_stack.last().unwrap().assume_u32(); + } + CrossModuleInternalCall => { + let module_idx = value_stack.last().unwrap().assume_u32() as usize; + let called_module = &self.modules[module_idx]; + out!(called_module.serialize_for_proof(&called_module.memory.merkelize())); + out!(mod_merkle + .prove(module_idx) + .expect("Failed to prove module for CrossModuleInternalCall")); + } + GetGlobalStateBytes32 | SetGlobalStateBytes32 => { + out!(self.global_state.serialize()); + let ptr = value_stack.last().unwrap().assume_u32(); if let Some(mut idx) = usize::try_from(ptr).ok().filter(|x| x % 32 == 0) { // Prove the leaf this index is in idx /= Memory::LEAF_SIZE; - data.extend(module.memory.get_leaf_data(idx)); - data.extend(mem_merkle.prove(idx).unwrap_or_default()); + out!(module.memory.get_leaf_data(idx)); + out!(mem_merkle.prove(idx).unwrap_or_default()); } - } else if matches!( - next_inst.opcode, - Opcode::ReadPreImage | Opcode::ReadInboxMessage, - ) { - let offset = self.value_stack.last().unwrap().assume_u32(); - let ptr = self - .value_stack - .get(self.value_stack.len() - 2) - .unwrap() - .assume_u32(); + } + ReadPreImage | ReadInboxMessage => { + let offset = value_stack.last().unwrap().assume_u32(); + let ptr = value_stack.get(value_stack.len() - 2).unwrap().assume_u32(); if let Some(mut idx) = usize::try_from(ptr).ok().filter(|x| x % 32 == 0) { // Prove the leaf this index is in idx /= Memory::LEAF_SIZE; let prev_data = module.memory.get_leaf_data(idx); - data.extend(prev_data); - data.extend(mem_merkle.prove(idx).unwrap_or_default()); - if next_inst.opcode == Opcode::ReadPreImage { + out!(prev_data); + out!(mem_merkle.prove(idx).unwrap_or_default()); + if op == Opcode::ReadPreImage { let hash = Bytes32(prev_data); let preimage_ty = PreimageType::try_from( u8::try_from(next_inst.argument_data) .expect("ReadPreImage argument data is out of range for a u8"), ) .expect("Invalid preimage type in ReadPreImage argument data"); - let preimage = - match self - .preimage_resolver + let Some(preimage) = + self.preimage_resolver .get_const(self.context, preimage_ty, hash) - { - Some(b) => b, - None => CBytes::new(), - }; + else { + panic!("Missing requested preimage for hash {}", hash) + }; data.push(0); // preimage proof type match preimage_ty { PreimageType::Keccak256 | PreimageType::Sha2_256 => { @@ -2341,31 +3055,96 @@ impl Machine { } } } else if next_inst.opcode == Opcode::ReadInboxMessage { - let msg_idx = self - .value_stack - .get(self.value_stack.len() - 3) - .unwrap() - .assume_u64(); - let inbox_identifier = argument_data_to_inbox(next_inst.argument_data) - .expect("Bad inbox indentifier"); + let msg_idx = value_stack.get(value_stack.len() - 3).unwrap().assume_u64(); + let inbox_identifier = + argument_data_to_inbox(arg).expect("Bad inbox indentifier"); if let Some(msg_data) = self.inbox_contents.get(&(inbox_identifier, msg_idx)) { data.push(0); // inbox proof type - data.extend(msg_data); + out!(msg_data); } } else { - panic!("Should never ever get here"); + unreachable!() } } } - } + LinkModule | UnlinkModule => { + if op == LinkModule { + let leaf_index = match value_stack.last() { + Some(Value::I32(x)) => *x as usize / Memory::LEAF_SIZE, + x => fail!("module pointer has invalid type {x:?}"), + }; + out!(module.memory.get_leaf_data(leaf_index)); + out!(mem_merkle.prove(leaf_index).unwrap_or_default()); + } + // prove that our proposed leaf x has a leaf-like hash + let module = self.modules.last().unwrap(); + out!(module.serialize_for_proof(&module.memory.merkelize())); + + // prove that leaf x is under the root at position p + let leaf = self.modules.len() - 1; + out!((leaf as u32).to_be_bytes()); + out!(mod_merkle.prove(leaf).unwrap()); + + // if needed, prove that x is the last module by proving that leaf p + 1 is 0 + let balanced = math::is_power_of_2(leaf + 1); + if !balanced { + out!(mod_merkle.prove_any(leaf + 1)); + } + } + PopCoThread => { + macro_rules! prove_pop { + ($multistack:expr, $hasher:expr) => { + let len = $multistack.len(); + if (len > 2) { + out!($hasher($multistack[len - 2])); + } else { + out!(Machine::NO_STACK_HASH); + } + if (len > 3) { + out!(hash_multistack(&$multistack[1..len - 2], $hasher)); + } else { + out!(Bytes32::default()); + } + }; + } + prove_pop!(self.get_data_stacks(), hash_value_stack); + prove_pop!(self.get_frame_stacks(), hash_stack_frame_stack); + } + _ => {} + } data } pub fn get_data_stack(&self) -> &[Value] { - &self.value_stack + match self.thread_state { + ThreadState::Main => &self.value_stacks[0], + ThreadState::CoThread(_) => self.value_stacks.last().unwrap(), + } + } + + pub fn get_data_stacks(&self) -> Vec<&[Value]> { + self.value_stacks.iter().map(|v| v.as_slice()).collect() + } + + fn get_frame_stack(&self) -> &[StackFrame] { + match self.thread_state { + ThreadState::Main => &self.frame_stacks[0], + ThreadState::CoThread(_) => self.frame_stacks.last().unwrap(), + } + } + + fn get_frame_stacks(&self) -> Vec<&[StackFrame]> { + self.frame_stacks + .iter() + .map(|v: &Vec<_>| v.as_slice()) + .collect() + } + + pub fn get_internals_stack(&self) -> &[Value] { + &self.internal_stack } pub fn get_global_state(&self) -> GlobalState { @@ -2395,35 +3174,43 @@ impl Machine { self.modules.get(module).map(|m| &*m.names) } - pub fn get_backtrace(&self) -> Vec<(String, String, usize)> { - let mut res = Vec::new(); - let mut push_pc = |pc: ProgramCounter| { + pub fn print_backtrace(&self, stderr: bool) { + let print = |line: String| match stderr { + true => println!("{}", line), + false => eprintln!("{}", line), + }; + + let print_pc = |pc: ProgramCounter| { let names = &self.modules[pc.module()].names; let func = names .functions .get(&pc.func) .cloned() - .unwrap_or_else(|| format!("{}", pc.func)); - let mut module = names.module.clone(); - if module.is_empty() { - module = format!("{}", pc.module); - } - res.push((module, func, pc.inst())); + .unwrap_or_else(|| pc.func.to_string()); + let func = rustc_demangle::demangle(&func); + let module = match names.module.is_empty() { + true => pc.module.to_string(), + false => names.module.clone(), + }; + let inst = format!("#{}", pc.inst); + print(format!( + " {} {} {} {}", + module.grey(), + func.mint(), + "inst".grey(), + inst.blue(), + )); }; - push_pc(self.pc); - for frame in self.frame_stack.iter().rev() { + + print_pc(self.pc); + let frame_stack = self.get_frame_stack(); + for frame in frame_stack.iter().rev().take(25) { if let Value::InternalRef(pc) = frame.return_ref { - push_pc(pc); + print_pc(pc); } } - res - } - - pub fn eprint_backtrace(&self) { - eprintln!("Backtrace:"); - for (module, func, pc) in self.get_backtrace() { - let func = rustc_demangle::demangle(&func); - eprintln!(" {} {} @ {}", module, func.mint(), pc.blue()); + if frame_stack.len() > 25 { + print(format!(" ... and {} more", frame_stack.len() - 25).grey()); } } } diff --git a/arbitrator/prover/src/main.rs b/arbitrator/prover/src/main.rs index e2681ebbc..6653d8bdc 100644 --- a/arbitrator/prover/src/main.rs +++ b/arbitrator/prover/src/main.rs @@ -1,12 +1,14 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use arbutil::{format, Color, DebugColor, PreimageType}; -use eyre::{Context, Result}; +#![cfg(feature = "native")] + +use arbutil::{format, Bytes32, Color, DebugColor, PreimageType}; +use eyre::{eyre, Context, Result}; use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use prover::{ machine::{GlobalState, InboxIdentifier, Machine, MachineStatus, PreimageResolver, ProofInfo}, - utils::{hash_preimage, Bytes32, CBytes}, + utils::{file_bytes, hash_preimage, CBytes}, wavm::Opcode, }; use std::sync::Arc; @@ -34,6 +36,11 @@ struct Opts { inbox_add_stub_headers: bool, #[structopt(long)] always_merkleize: bool, + #[structopt(long)] + debug_funcs: bool, + #[structopt(long)] + /// print modules to the console + print_modules: bool, /// profile output instead of generting proofs #[structopt(short = "p", long)] profile_run: bool, @@ -66,6 +73,8 @@ struct Opts { delayed_inbox: Vec, #[structopt(long)] preimages: Option, + #[structopt(long)] + stylus_modules: Vec, /// Require that the machine end in the Finished state #[structopt(long)] require_success: bool, @@ -109,6 +118,7 @@ struct SimpleProfile { const INBOX_HEADER_LEN: usize = 40; // also in test-case's host-io.rs & contracts's OneStepProverHostIo.sol const DELAYED_HEADER_LEN: usize = 112; // also in test-case's host-io.rs & contracts's OneStepProverHostIo.sol +#[cfg(feature = "native")] fn main() -> Result<()> { let opts = Opts::from_args(); @@ -185,10 +195,25 @@ fn main() -> Result<()> { true, opts.always_merkleize, opts.allow_hostapi, + opts.debug_funcs, + true, global_state, inbox_contents, preimage_resolver, )?; + + for path in &opts.stylus_modules { + let err = || eyre!("failed to read module at {}", path.to_string_lossy().red()); + let wasm = file_bytes(path).wrap_err_with(err)?; + let codehash = &Bytes32::default(); + mach.add_program(&wasm, codehash, 1, true) + .wrap_err_with(err)?; + } + + if opts.print_modules { + mach.print_modules(); + } + if let Some(output_path) = opts.generate_binaries { let mut module_root_file = File::create(output_path.join("module-root.txt"))?; writeln!(module_root_file, "0x{}", mach.get_modules_root())?; @@ -239,7 +264,10 @@ fn main() -> Result<()> { if opts.proving_backoff { let mut extra_data = 0; - if matches!(next_opcode, Opcode::ReadInboxMessage | Opcode::ReadPreImage) { + if matches!( + next_opcode, + Opcode::ReadInboxMessage | Opcode::ReadPreImage | Opcode::SwitchThread + ) { extra_data = next_inst.argument_data; } let count_entry = proving_backoff @@ -320,9 +348,13 @@ fn main() -> Result<()> { } } else { let values = mach.get_data_stack(); + let inters = mach.get_internals_stack(); if !values.is_empty() { println!("{} {}", "Machine stack".grey(), format::commas(values)); } + if !inters.is_empty() { + println!("{} {}", "Internals ".grey(), format::commas(inters)); + } print!( "Generating proof {} (inst {}) for {}{}", proofs.len().blue(), @@ -375,10 +407,7 @@ fn main() -> Result<()> { println!("End machine hash: {}", mach.hash()); println!("End machine stack: {:?}", mach.get_data_stack()); println!("End machine backtrace:"); - for (module, func, pc) in mach.get_backtrace() { - let func = rustc_demangle::demangle(&func); - println!(" {} {} @ {}", module, func.mint(), pc.blue()); - } + mach.print_backtrace(false); if let Some(out) = opts.output { let out = File::create(out)?; @@ -426,14 +455,11 @@ fn main() -> Result<()> { let opts_binary = opts.binary; let opts_libraries = opts.libraries; let format_pc = |module_num: usize, func_num: usize| -> (String, String) { - let names = match mach.get_module_names(module_num) { - Some(n) => n, - None => { - return ( - format!("[unknown {}]", module_num), - format!("[unknown {}]", func_num), - ); - } + let Some(names) = mach.get_module_names(module_num) else { + return ( + format!("[unknown {}]", module_num), + format!("[unknown {}]", func_num), + ); }; let module_name = if module_num == 0 { names.module.clone() @@ -504,6 +530,5 @@ fn main() -> Result<()> { eprintln!("Machine didn't finish: {}", mach.get_status().red()); std::process::exit(1); } - Ok(()) } diff --git a/arbitrator/prover/src/memory.rs b/arbitrator/prover/src/memory.rs index 8cf5b8e94..bd9622109 100644 --- a/arbitrator/prover/src/memory.rs +++ b/arbitrator/prover/src/memory.rs @@ -3,14 +3,46 @@ use crate::{ merkle::{Merkle, MerkleType}, - utils::Bytes32, value::{ArbValueType, Value}, }; +use arbutil::Bytes32; use digest::Digest; -use rayon::prelude::*; +use eyre::{bail, ErrReport, Result}; use serde::{Deserialize, Serialize}; use sha3::Keccak256; use std::{borrow::Cow, convert::TryFrom}; +use wasmer_types::Pages; + +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +pub struct MemoryType { + pub min: Pages, + pub max: Option, +} + +impl MemoryType { + pub fn new(min: Pages, max: Option) -> Self { + Self { min, max } + } +} + +impl From<&wasmer_types::MemoryType> for MemoryType { + fn from(value: &wasmer_types::MemoryType) -> Self { + Self::new(value.minimum, value.maximum) + } +} + +impl TryFrom<&wasmparser::MemoryType> for MemoryType { + type Error = ErrReport; + + fn try_from(value: &wasmparser::MemoryType) -> std::result::Result { + Ok(Self { + min: Pages(value.initial.try_into()?), + max: value.maximum.map(|x| x.try_into()).transpose()?.map(Pages), + }) + } +} #[derive(PartialEq, Eq, Clone, Debug, Default, Serialize, Deserialize)] pub struct Memory { @@ -72,9 +104,14 @@ impl Memory { } // Round the size up to 8 byte long leaves, then round up to the next power of two number of leaves let leaves = round_up_to_power_of_two(div_round_up(self.buffer.len(), Self::LEAF_SIZE)); - let mut leaf_hashes: Vec = self - .buffer - .par_chunks(Self::LEAF_SIZE) + + #[cfg(feature = "rayon")] + let leaf_hashes = self.buffer.par_chunks(Self::LEAF_SIZE); + + #[cfg(not(feature = "rayon"))] + let leaf_hashes = self.buffer.chunks(Self::LEAF_SIZE); + + let mut leaf_hashes: Vec = leaf_hashes .map(|leaf| { let mut full_leaf = [0u8; 32]; full_leaf[..leaf.len()].copy_from_slice(leaf); @@ -174,11 +211,11 @@ impl Memory { ArbValueType::I64 => Value::I64(contents as u64), ArbValueType::F32 => { assert!(bytes == 4 && !signed, "Invalid source for f32"); - Value::F32(f32::from_bits(contents as u32)) + f32::from_bits(contents as u32).into() } ArbValueType::F64 => { assert!(bytes == 8 && !signed, "Invalid source for f64"); - Value::F64(f64::from_bits(contents as u64)) + f64::from_bits(contents as u64).into() } _ => panic!("Invalid memory load output type {:?}", ty), }) @@ -186,9 +223,8 @@ impl Memory { #[must_use] pub fn store_value(&mut self, idx: u64, value: u64, bytes: u8) -> bool { - let end_idx = match idx.checked_add(bytes.into()) { - Some(x) => x, - None => return false, + let Some(end_idx) = idx.checked_add(bytes.into()) else { + return false; }; if end_idx > self.buffer.len() as u64 { return false; @@ -216,9 +252,8 @@ impl Memory { if idx % Self::LEAF_SIZE as u64 != 0 { return false; } - let end_idx = match idx.checked_add(value.len() as u64) { - Some(x) => x, - None => return false, + let Some(end_idx) = idx.checked_add(value.len() as u64) else { + return false; }; if end_idx > self.buffer.len() as u64 { return false; @@ -242,9 +277,8 @@ impl Memory { if idx % Self::LEAF_SIZE as u64 != 0 { return None; } - let idx = match usize::try_from(idx) { - Ok(x) => x, - Err(_) => return None, + let Ok(idx) = usize::try_from(idx) else { + return None; }; let slice = self.get_range(idx, 32)?; @@ -261,12 +295,13 @@ impl Memory { Some(&self.buffer[offset..end]) } - pub fn set_range(&mut self, offset: usize, data: &[u8]) { + pub fn set_range(&mut self, offset: usize, data: &[u8]) -> Result<()> { self.merkle = None; - let end = offset - .checked_add(data.len()) - .expect("Overflow in offset+data.len() in Memory::set_range"); + let Some(end) = offset.checked_add(data.len()) else { + bail!("Overflow in offset+data.len() in Memory::set_range") + }; self.buffer[offset..end].copy_from_slice(data); + Ok(()) } pub fn cache_merkle_tree(&mut self) { diff --git a/arbitrator/prover/src/merkle.rs b/arbitrator/prover/src/merkle.rs index 6a4c3dac1..16306bd61 100644 --- a/arbitrator/prover/src/merkle.rs +++ b/arbitrator/prover/src/merkle.rs @@ -1,13 +1,16 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use crate::utils::Bytes32; +use arbutil::Bytes32; use digest::Digest; -use rayon::prelude::*; +use serde::{Deserialize, Serialize}; use sha3::Keccak256; use std::convert::TryFrom; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum MerkleType { Empty, Value, @@ -40,11 +43,12 @@ impl MerkleType { } } -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct Merkle { ty: MerkleType, layers: Vec>, empty_layers: Vec, + min_depth: usize, } fn hash_node(ty: MerkleType, a: Bytes32, b: Bytes32) -> Bytes32 { @@ -73,13 +77,15 @@ impl Merkle { let mut empty_layers = vec![empty_hash]; while layers.last().unwrap().len() > 1 || layers.len() < min_depth { let empty_layer = *empty_layers.last().unwrap(); - let new_layer = layers - .last() - .unwrap() - .par_chunks(2) - .map(|window| { - hash_node(ty, window[0], window.get(1).cloned().unwrap_or(empty_layer)) - }) + + #[cfg(feature = "rayon")] + let new_layer = layers.last().unwrap().par_chunks(2); + + #[cfg(not(feature = "rayon"))] + let new_layer = layers.last().unwrap().chunks(2); + + let new_layer = new_layer + .map(|chunk| hash_node(ty, chunk[0], chunk.get(1).cloned().unwrap_or(empty_layer))) .collect(); empty_layers.push(hash_node(ty, empty_layer, empty_layer)); layers.push(new_layer); @@ -88,6 +94,7 @@ impl Merkle { ty, layers, empty_layers, + min_depth, } } @@ -109,10 +116,16 @@ impl Merkle { } #[must_use] - pub fn prove(&self, mut idx: usize) -> Option> { + pub fn prove(&self, idx: usize) -> Option> { if idx >= self.leaves().len() { return None; } + Some(self.prove_any(idx)) + } + + /// creates a merkle proof regardless of if the leaf has content + #[must_use] + pub fn prove_any(&self, mut idx: usize) -> Vec { let mut proof = vec![u8::try_from(self.layers.len() - 1).unwrap()]; for (layer_i, layer) in self.layers.iter().enumerate() { if layer_i == self.layers.len() - 1 { @@ -127,7 +140,25 @@ impl Merkle { ); idx >>= 1; } - Some(proof) + proof + } + + /// Adds a new leaf to the merkle + /// Currently O(n) in the number of leaves (could be log(n)) + pub fn push_leaf(&mut self, leaf: Bytes32) { + let mut leaves = self.layers.swap_remove(0); + leaves.push(leaf); + let empty = self.empty_layers[0]; + *self = Self::new_advanced(self.ty, leaves, empty, self.min_depth); + } + + /// Removes the rightmost leaf from the merkle + /// Currently O(n) in the number of leaves (could be log(n)) + pub fn pop_leaf(&mut self) { + let mut leaves = self.layers.swap_remove(0); + leaves.pop(); + let empty = self.empty_layers[0]; + *self = Self::new_advanced(self.ty, leaves, empty, self.min_depth); } pub fn set(&mut self, mut idx: usize, hash: Bytes32) { diff --git a/arbitrator/prover/src/print.rs b/arbitrator/prover/src/print.rs new file mode 100644 index 000000000..138a01f4b --- /dev/null +++ b/arbitrator/prover/src/print.rs @@ -0,0 +1,303 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ + host::InternalFunc, + machine::Module, + value::{FunctionType, Value}, + wavm::{self, Opcode}, +}; +use arbutil::Color; +use fnv::FnvHashSet as HashSet; +use num_traits::FromPrimitive; +use std::fmt::{self, Display}; +use wasmer_types::WASM_PAGE_SIZE; + +impl FunctionType { + fn wat_string(&self, name_args: bool) -> String { + let params = if !self.inputs.is_empty() { + let inputs = self.inputs.iter().enumerate(); + let params = inputs.fold(String::new(), |acc, (j, ty)| match name_args { + true => format!("{acc} {} {}", format!("$arg{j}").pink(), ty.mint()), + false => format!("{acc} {}", ty.mint()), + }); + format!(" ({}{params})", "param".grey()) + } else { + String::new() + }; + + let results = if !self.outputs.is_empty() { + let outputs = self.outputs.iter(); + let results = outputs.fold(String::new(), |acc, t| format!("{acc} {t}")); + format!(" ({}{})", "result".grey(), results.mint()) + } else { + String::new() + }; + + format!("{params}{results}") + } +} + +impl Module { + fn func_name(&self, i: u32) -> String { + match self.maybe_func_name(i) { + Some(func) => format!("${func}"), + None => format!("$func_{i}"), + } + .pink() + } + + fn maybe_func_name(&self, i: u32) -> Option { + if let Some(name) = self.names.functions.get(&i) { + Some(name.to_owned()) + } else if i >= self.internals_offset { + InternalFunc::from_u32(i - self.internals_offset).map(|f| format!("{f:?}")) + } else { + None + } + } +} + +impl Display for Module { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut pad = 0; + + macro_rules! w { + ($($args:expr),*) => {{ + let text = format!($($args),*); + write!(f, "{:pad$}{text}", "")?; + }}; + } + macro_rules! wln { + ($($args:expr),*) => {{ + w!($($args),*); + writeln!(f)?; + }}; + } + + wln!("({} {}", "module".grey(), self.name().mint()); + pad += 4; + + for ty in &*self.types { + let ty = ty.wat_string(false); + wln!("({} ({}{ty}))", "type".grey(), "func".grey()); + } + + for (i, hook) in self.host_call_hooks.iter().enumerate() { + if let Some((module, func)) = hook { + wln!( + r#"({} "{}" "{}" ({} {}{}))"#, + "import".grey(), + module.pink(), + func.pink(), + "func".grey(), + self.func_name(i as u32), + self.funcs[i].ty.wat_string(false) + ); + } + } + + for (i, g) in self.globals.iter().enumerate() { + let global_label = format!("$global_{i}").pink(); + wln!("({} {global_label} {})", "global".grey(), g.mint()); + } + + for (i, table) in self.tables.iter().enumerate() { + let ty = table.ty; + let initial = format!("{}", ty.initial).mint(); + let max = ty.maximum.map(|x| format!(" {x}")).unwrap_or_default(); + let type_str = format!("{:?}", ty.element_type).mint(); + w!( + "({} {} {initial} {}{type_str}", + "table".grey(), + format!("$table_{i}").pink(), + max.mint() + ); + + pad += 4; + let mut empty = true; + let mut segment = vec![]; + let mut start = None; + let mut end = 0; + for (j, elem) in table.elems.iter().enumerate() { + if let Value::FuncRef(id) = elem.val { + segment.push(self.func_name(id)); + start.get_or_insert(j); + end = j; + empty = false; + } + + let last = j == table.elems.len() - 1; + if (last || matches!(elem.val, Value::RefNull)) && !segment.is_empty() { + let start = start.unwrap(); + wln!(""); + w!("{}", format!("[{start:#05x}-{end:#05x}]:").grey()); + for item in &segment { + write!(f, " {item}")?; + } + segment.clear(); + } + } + pad -= 4; + if !empty { + wln!(""); + w!(""); + } + writeln!(f, ")")?; + } + + let args = format!( + "{} {}", + self.memory.size() / WASM_PAGE_SIZE as u64, + self.memory.max_size + ); + w!("({} {}", "memory".grey(), args.mint()); + + pad += 4; + let mut empty = true; + let mut segment = None; + for index in 0..self.memory.size() { + let byte = self.memory.get_u8(index).unwrap(); + + // start new segment + if byte != 0 && segment.is_none() { + segment = Some(index as usize); + empty = false; + } + + // print the segment + if (byte == 0x00 || index == self.memory.size() - 1) && segment.is_some() { + let start = segment.unwrap(); + let end = index - 1 + (byte != 0x00) as u64; + let len = end as usize - start + 1; + let range = format!("[{start:#06x}-{end:#06x}]"); + let data = self.memory.get_range(start, len).unwrap(); + wln!(""); + w!("{}: {}", range.grey(), hex::encode(data).yellow()); + segment = None; + } + } + pad -= 4; + if !empty { + wln!(""); + w!(""); + } + writeln!(f, ")")?; + + for (i, func) in self.funcs.iter().enumerate() { + let i1 = i as u32; + let padding = 12; + + let export_str = match self.maybe_func_name(i1) { + Some(name) => { + let description = if (i1 as usize) < self.host_call_hooks.len() { + "import" + } else { + "export" + }; + format!(r#" ({} "{}")"#, description.grey(), name.pink()) + } + None => format!(" $func_{i}").pink(), + }; + w!( + "({}{}{}", + "func".grey(), + export_str, + func.ty.wat_string(true) + ); + + pad += 4; + if !func.local_types.is_empty() { + write!(f, " ({}", "local".grey())?; + for (i, ty) in func.local_types.iter().enumerate() { + let local_str = format!("$local_{i}"); + write!(f, " {} {}", local_str.pink(), ty.mint())?; + } + write!(f, ")")?; + } + writeln!(f)?; + + let mut labels = HashSet::default(); + use Opcode::*; + for op in func.code.iter() { + if op.opcode == ArbitraryJump || op.opcode == ArbitraryJumpIf { + labels.insert(op.argument_data as usize); + } + } + + for (j, op) in func.code.iter().enumerate() { + let op_str = format!("{:?}", op.opcode).grey(); + let arg_str = match op.opcode { + ArbitraryJump | ArbitraryJumpIf => { + match labels.get(&(op.argument_data as usize)) { + Some(label) => format!(" label_${label}").pink(), + None => " ???".to_string().red(), + } + } + Call + | CallerModuleInternalCall + | CrossModuleForward + | CrossModuleInternalCall => { + format!(" {}", self.func_name(op.argument_data as u32)) + } + CrossModuleCall => { + let (module, func) = wavm::unpack_cross_module_call(op.argument_data); + format!( + " {} {}", + format!("{module}").mint(), + format!("{func}").mint() + ) + } + CallIndirect => { + let (table_index, type_index) = + wavm::unpack_call_indirect(op.argument_data); + format!( + " {} {}", + self.types[type_index as usize].pink(), + format!("{table_index}").mint() + ) + } + F32Const | F64Const | I32Const | I64Const => { + format!(" {:#x}", op.argument_data).mint() + } + GlobalGet | GlobalSet => format!(" $global_{}", op.argument_data).pink(), + LocalGet | LocalSet => format!(" $local_{}", op.argument_data).pink(), + MemoryLoad { .. } | MemoryStore { .. } | ReadInboxMessage => { + format!(" {:#x}", op.argument_data).mint() + } + _ => { + if op.argument_data == 0 { + String::new() + } else { + format!(" UNEXPECTED_ARG: {}", op.argument_data).mint() + } + } + }; + + let proof = op + .proving_argument_data + .map(hex::encode) + .unwrap_or_default() + .orange(); + + match labels.get(&j) { + Some(label) => { + let label = format!("label_{label}"); + let spaces = padding - label.len() - 1; + wln!("{}:{:spaces$}{op_str}{arg_str} {proof}", label.pink(), "") + } + None => wln!("{:padding$}{op_str}{arg_str} {proof}", ""), + } + } + pad -= 4; + wln!(")"); + } + + if let Some(start) = self.start_function { + wln!("({} {})", "start".grey(), self.func_name(start)); + } + pad -= 4; + wln!(")"); + Ok(()) + } +} diff --git a/arbitrator/prover/src/programs/config.rs b/arbitrator/prover/src/programs/config.rs new file mode 100644 index 000000000..0b5ce1747 --- /dev/null +++ b/arbitrator/prover/src/programs/config.rs @@ -0,0 +1,222 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::field_reassign_with_default)] + +use crate::{programs::meter, value::FunctionType}; +use derivative::Derivative; +use fnv::FnvHashMap as HashMap; +use std::fmt::Debug; +use wasmer_types::{Pages, SignatureIndex, WASM_PAGE_SIZE}; +use wasmparser::Operator; + +#[cfg(feature = "native")] +use { + super::{ + counter::Counter, depth::DepthChecker, dynamic::DynamicMeter, heap::HeapBound, + meter::Meter, start::StartMover, MiddlewareWrapper, + }, + std::sync::Arc, + wasmer::{Cranelift, CraneliftOptLevel, Engine, Store}, + wasmer_compiler_singlepass::Singlepass, +}; + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct StylusConfig { + /// Version the program was compiled against + pub version: u16, + /// The maximum size of the stack, measured in words + pub max_depth: u32, + /// Pricing parameters supplied at runtime + pub pricing: PricingParams, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct PricingParams { + /// The price of ink, measured in bips of an evm gas + pub ink_price: u32, +} + +impl Default for StylusConfig { + fn default() -> Self { + Self { + version: 0, + max_depth: u32::MAX, + pricing: PricingParams::default(), + } + } +} + +impl Default for PricingParams { + fn default() -> Self { + Self { ink_price: 1 } + } +} + +impl StylusConfig { + pub const fn new(version: u16, max_depth: u32, ink_price: u32) -> Self { + let pricing = PricingParams::new(ink_price); + Self { + version, + max_depth, + pricing, + } + } +} + +#[allow(clippy::inconsistent_digit_grouping)] +impl PricingParams { + pub const fn new(ink_price: u32) -> Self { + Self { ink_price } + } + + pub fn gas_to_ink(&self, gas: u64) -> u64 { + gas.saturating_mul(self.ink_price.into()) + } + + pub fn ink_to_gas(&self, ink: u64) -> u64 { + ink / self.ink_price as u64 // never 0 + } +} + +pub type SigMap = HashMap; +pub type OpCosts = fn(&Operator, &SigMap) -> u64; + +#[derive(Clone, Debug, Default)] +pub struct CompileConfig { + /// Version of the compiler to use + pub version: u16, + /// Pricing parameters used for metering + pub pricing: CompilePricingParams, + /// Memory bounds + pub bounds: CompileMemoryParams, + /// Debug parameters for test chains + pub debug: CompileDebugParams, +} + +#[derive(Clone, Copy, Debug)] +pub struct CompileMemoryParams { + /// The maximum number of pages a program may start with + pub heap_bound: Pages, + /// The maximum size of a stack frame, measured in words + pub max_frame_size: u32, + /// The maximum number of overlapping value lifetimes in a frame + pub max_frame_contention: u16, +} + +#[derive(Clone, Derivative)] +#[derivative(Debug)] +pub struct CompilePricingParams { + /// Associates opcodes to their ink costs + #[derivative(Debug = "ignore")] + pub costs: OpCosts, + /// Cost of checking the amount of ink left. + pub ink_header_cost: u64, + /// Per-byte `MemoryFill` cost + pub memory_fill_ink: u64, + /// Per-byte `MemoryCopy` cost + pub memory_copy_ink: u64, +} + +#[derive(Clone, Debug, Default)] +pub struct CompileDebugParams { + /// Allow debug functions + pub debug_funcs: bool, + /// Retain debug info + pub debug_info: bool, + /// Add instrumentation to count the number of times each kind of opcode is executed + pub count_ops: bool, + /// Whether to use the Cranelift compiler + pub cranelift: bool, +} + +impl Default for CompilePricingParams { + fn default() -> Self { + Self { + costs: |_, _| 0, + ink_header_cost: 0, + memory_fill_ink: 0, + memory_copy_ink: 0, + } + } +} + +impl Default for CompileMemoryParams { + fn default() -> Self { + Self { + heap_bound: Pages(u32::MAX / WASM_PAGE_SIZE as u32), + max_frame_size: u32::MAX, + max_frame_contention: u16::MAX, + } + } +} + +impl CompileConfig { + pub fn version(version: u16, debug_chain: bool) -> Self { + let mut config = Self::default(); + config.version = version; + config.debug.debug_funcs = debug_chain; + config.debug.debug_info = debug_chain; + + match version { + 0 => {} + 1 => { + // TODO: settle on reasonable values for the v1 release + config.bounds.heap_bound = Pages(128); // 8 mb + config.bounds.max_frame_size = 10 * 1024; + config.bounds.max_frame_contention = 4096; + config.pricing = CompilePricingParams { + costs: meter::pricing_v1, + ink_header_cost: 2450, + memory_fill_ink: 800 / 8, + memory_copy_ink: 800 / 8, + }; + } + _ => panic!("no config exists for Stylus version {version}"), + } + + config + } + + #[cfg(feature = "native")] + pub fn store(&self) -> Store { + let mut compiler: Box = match self.debug.cranelift { + true => { + let mut compiler = Cranelift::new(); + compiler.opt_level(CraneliftOptLevel::Speed); + Box::new(compiler) + } + false => Box::new(Singlepass::new()), + }; + compiler.canonicalize_nans(true); + compiler.enable_verifier(); + + let start = MiddlewareWrapper::new(StartMover::new(self.debug.debug_info)); + let meter = MiddlewareWrapper::new(Meter::new(&self.pricing)); + let dygas = MiddlewareWrapper::new(DynamicMeter::new(&self.pricing)); + let depth = MiddlewareWrapper::new(DepthChecker::new(self.bounds)); + let bound = MiddlewareWrapper::new(HeapBound::new(self.bounds)); + + // add the instrumentation in the order of application + // note: this must be consistent with the prover + compiler.push_middleware(Arc::new(start)); + compiler.push_middleware(Arc::new(meter)); + compiler.push_middleware(Arc::new(dygas)); + compiler.push_middleware(Arc::new(depth)); + compiler.push_middleware(Arc::new(bound)); + + if self.debug.count_ops { + let counter = Counter::new(); + compiler.push_middleware(Arc::new(MiddlewareWrapper::new(counter))); + } + + Store::new(compiler) + } + + #[cfg(feature = "native")] + pub fn engine(&self) -> Engine { + self.store().engine().clone() + } +} diff --git a/arbitrator/prover/src/programs/counter.rs b/arbitrator/prover/src/programs/counter.rs new file mode 100644 index 000000000..cd54178cf --- /dev/null +++ b/arbitrator/prover/src/programs/counter.rs @@ -0,0 +1,155 @@ +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use super::{FuncMiddleware, Middleware, ModuleMod}; +use crate::Machine; + +use arbutil::operator::{OperatorCode, OperatorInfo}; +use eyre::{eyre, Result}; +use fnv::FnvHashMap as HashMap; +use lazy_static::lazy_static; +use parking_lot::Mutex; +use std::collections::BTreeMap; +use std::{clone::Clone, fmt::Debug, sync::Arc}; +use wasmer_types::{GlobalIndex, GlobalInit, LocalFunctionIndex, Type}; +use wasmparser::Operator; + +lazy_static! { + /// Assigns each operator a sequential offset + pub static ref OP_OFFSETS: Mutex> = Mutex::new(HashMap::default()); +} + +#[derive(Debug)] +pub struct Counter { + /// Assigns each relative offset a global variable + pub counters: Arc>>, +} + +impl Counter { + pub fn new() -> Self { + let counters = Arc::new(Mutex::new(Vec::with_capacity(OperatorCode::OPERATOR_COUNT))); + Self { counters } + } + + pub fn global_name(index: usize) -> String { + format!("stylus_opcode{}_count", index) + } +} + +impl Default for Counter { + fn default() -> Self { + Self::new() + } +} + +impl Middleware for Counter +where + M: ModuleMod, +{ + type FM<'a> = FuncCounter<'a>; + + fn update_module(&self, module: &mut M) -> Result<()> { + let mut counters = self.counters.lock(); + for index in 0..OperatorCode::OPERATOR_COUNT { + let zero_count = GlobalInit::I64Const(0); + let global = module.add_global(&Self::global_name(index), Type::I64, zero_count)?; + counters.push(global); + } + Ok(()) + } + + fn instrument<'a>(&self, _: LocalFunctionIndex) -> Result> { + Ok(FuncCounter::new(self.counters.clone())) + } + + fn name(&self) -> &'static str { + "operator counter" + } +} + +#[derive(Debug)] +pub struct FuncCounter<'a> { + /// Assigns each relative offset a global variable + counters: Arc>>, + /// Instructions of the current basic block + block: Vec>, +} + +impl<'a> FuncCounter<'a> { + fn new(counters: Arc>>) -> Self { + let block = vec![]; + Self { counters, block } + } +} + +impl<'a> FuncMiddleware<'a> for FuncCounter<'a> { + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>, + { + use Operator::*; + + let end = op.ends_basic_block(); + self.block.push(op); + + if end { + let update = |global_index: u32, value: i64| { + [ + GlobalGet { global_index }, + I64Const { value }, + I64Add, + GlobalSet { global_index }, + ] + }; + + // there's always at least one op, so we chain the instrumentation + let mut increments = HashMap::default(); + for op in self.block.iter().chain(update(0, 0).iter()) { + let count = increments.entry(op.code()).or_default(); + *count += 1; + } + + // add the instrumentation's contribution to the overall counts + let kinds = increments.len() as i64; + for op in update(0, 0) { + let count = increments.get_mut(&op.code()).unwrap(); + *count += kinds - 1; // we included one in the last loop + } + + let counters = self.counters.lock(); + let mut operators = OP_OFFSETS.lock(); + for (op, count) in increments { + let opslen = operators.len(); + let offset = *operators.entry(op).or_insert(opslen); + let global = *counters.get(offset).ok_or_else(|| eyre!("no global"))?; + out.extend(update(global.as_u32(), count)); + } + + out.extend(self.block.drain(..)); + } + Ok(()) + } + + fn name(&self) -> &'static str { + "operator counter" + } +} + +pub trait CountingMachine { + fn operator_counts(&mut self) -> Result>; +} + +impl CountingMachine for Machine { + fn operator_counts(&mut self) -> Result> { + let mut counts = BTreeMap::new(); + + for (&op, &offset) in OP_OFFSETS.lock().iter() { + let count = self.get_global(&Counter::global_name(offset))?; + let count: u64 = count.try_into()?; + if count != 0 { + counts.insert(op, count); + } + } + Ok(counts) + } +} diff --git a/arbitrator/prover/src/programs/depth.rs b/arbitrator/prover/src/programs/depth.rs new file mode 100644 index 000000000..200019091 --- /dev/null +++ b/arbitrator/prover/src/programs/depth.rs @@ -0,0 +1,541 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use super::{ + config::{CompileMemoryParams, SigMap}, + FuncMiddleware, Middleware, ModuleMod, +}; +use crate::{host::InternalFunc, value::FunctionType, Machine}; + +use arbutil::Color; +use eyre::{bail, Result}; +use fnv::FnvHashMap as HashMap; +use parking_lot::RwLock; +use std::sync::Arc; +use wasmer_types::{ + FunctionIndex, GlobalIndex, GlobalInit, LocalFunctionIndex, SignatureIndex, Type, +}; +use wasmparser::{BlockType, Operator, ValType}; + +pub const STYLUS_STACK_LEFT: &str = "stylus_stack_left"; + +/// This middleware ensures stack overflows are deterministic across different compilers and targets. +/// The internal notion of "stack space left" that makes this possible is strictly smaller than that of +/// the real stack space consumed on any target platform and is formed by inspecting the contents of each +/// function's frame. +/// Setting a limit smaller than that of any native platform's ensures stack overflows will have the same, +/// logical effect rather than actually exhausting the space provided by the OS. +#[derive(Debug)] +pub struct DepthChecker { + /// The amount of stack space left + global: RwLock>, + /// The maximum size of a stack frame, measured in words + frame_limit: u32, + /// The maximum number of overlapping value lifetimes in a frame + frame_contention: u16, + /// The function types of the module being instrumented + funcs: RwLock>>>, + /// The types of the module being instrumented + sigs: RwLock>>, +} + +impl DepthChecker { + pub fn new(params: CompileMemoryParams) -> Self { + Self { + global: RwLock::default(), + frame_limit: params.max_frame_size, + frame_contention: params.max_frame_contention, + funcs: RwLock::default(), + sigs: RwLock::default(), + } + } + + pub fn globals(&self) -> GlobalIndex { + self.global.read().unwrap() + } +} + +impl Middleware for DepthChecker { + type FM<'a> = FuncDepthChecker<'a>; + + fn update_module(&self, module: &mut M) -> Result<()> { + let limit = GlobalInit::I32Const(0); + let space = module.add_global(STYLUS_STACK_LEFT, Type::I32, limit)?; + *self.global.write() = Some(space); + *self.funcs.write() = Some(Arc::new(module.all_functions()?)); + *self.sigs.write() = Some(Arc::new(module.all_signatures()?)); + Ok(()) + } + + fn instrument<'a>(&self, func: LocalFunctionIndex) -> Result> { + Ok(FuncDepthChecker::new( + self.global.read().expect("no global"), + self.funcs.read().clone().expect("no funcs"), + self.sigs.read().clone().expect("no sigs"), + self.frame_limit, + self.frame_contention, + func, + )) + } + + fn name(&self) -> &'static str { + "depth checker" + } +} + +#[derive(Debug)] +pub struct FuncDepthChecker<'a> { + /// The amount of stack space left + global: GlobalIndex, + /// The function types in this function's module + funcs: Arc>, + /// All the types in this function's modules + sigs: Arc>, + /// The number of local variables this func has + locals: Option, + /// The function being instrumented + func: LocalFunctionIndex, + /// The maximum size of a stack frame, measured in words + frame_limit: u32, + /// The maximum number of overlapping value lifetimes in a frame + frame_contention: u16, + /// The number of open scopes + scopes: isize, + /// The entirety of the func's original instructions + code: Vec>, + /// True once it's statically known feed() won't be called again + done: bool, +} + +impl<'a> FuncDepthChecker<'a> { + fn new( + global: GlobalIndex, + funcs: Arc>, + sigs: Arc>, + frame_limit: u32, + frame_contention: u16, + func: LocalFunctionIndex, + ) -> Self { + Self { + global, + funcs, + sigs, + locals: None, + func, + frame_limit, + frame_contention, + scopes: 1, // a function starts with an open scope + code: vec![], + done: false, + } + } +} + +impl<'a> FuncMiddleware<'a> for FuncDepthChecker<'a> { + fn locals_info(&mut self, locals: &[ValType]) { + self.locals = Some(locals.len()); + } + + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>, + { + use Operator::*; + + // Knowing when the feed ends requires detecting the final instruction, which is + // guaranteed to be an "End" opcode closing out function's initial opening scope. + if self.done { + bail!("finalized too soon"); + } + + let scopes = &mut self.scopes; + match op { + Block { .. } | Loop { .. } | If { .. } => *scopes += 1, + End => *scopes -= 1, + _ => {} + } + if *scopes < 0 { + bail!("malformed scoping detected"); + } + + let last = *scopes == 0 && matches!(op, End); // true when the feed ends + self.code.push(op); + if !last { + return Ok(()); + } + + // We've reached the final instruction and can instrument the function as follows: + // - When entering, check that the stack has sufficient space and deduct the amount used + // - When returning, credit back the amount used + + let size = self.worst_case_depth()?; + let global_index = self.global.as_u32(); + + if size > self.frame_limit { + let limit = self.frame_limit.red(); + bail!("frame too large: {} > {}-word limit", size.red(), limit); + } + + let blockty = BlockType::Empty; + out.extend([ + // if space <= size => panic with depth = 0 + GlobalGet { global_index }, + I32Const { value: size as i32 }, + I32LeU, + If { blockty }, + I32Const { value: 0 }, + GlobalSet { global_index }, + Unreachable, + End, + // space -= size + GlobalGet { global_index }, + I32Const { value: size as i32 }, + I32Sub, + GlobalSet { global_index }, + ]); + + let reclaim = |out: &mut O| { + out.extend([ + // space += size + GlobalGet { global_index }, + I32Const { value: size as i32 }, + I32Add, + GlobalSet { global_index }, + ]) + }; + + // add an extraneous return instruction to the end to match Arbitrator + let mut code = std::mem::take(&mut self.code); + let last = code.pop().unwrap(); + code.push(Return); + code.push(last); + + for op in code { + let exit = matches!(op, Return); + if exit { + reclaim(out); + } + out.extend([op]); + } + + self.done = true; + Ok(()) + } + + fn name(&self) -> &'static str { + "depth checker" + } +} + +impl<'a> FuncDepthChecker<'a> { + fn worst_case_depth(&self) -> Result { + use Operator::*; + + let mut worst: u32 = 0; + let mut stack: u32 = 0; + + macro_rules! push { + ($count:expr) => {{ + stack += $count; + worst = worst.max(stack); + }}; + () => { + push!(1) + }; + } + macro_rules! pop { + ($count:expr) => {{ + stack = stack.saturating_sub($count); + }}; + () => { + pop!(1) + }; + } + macro_rules! ins_and_outs { + ($ty:expr) => {{ + let ins = $ty.inputs.len() as u32; + let outs = $ty.outputs.len() as u32; + push!(outs); + pop!(ins); + }}; + } + macro_rules! op { + ($first:ident $(,$opcode:ident)* $(,)?) => { + $first $(| $opcode)* + }; + } + macro_rules! dot { + ($first:ident $(,$opcode:ident)* $(,)?) => { + $first { .. } $(| $opcode { .. })* + }; + } + #[rustfmt::skip] + macro_rules! block_type { + ($ty:expr) => {{ + match $ty { + BlockType::Empty => {} + BlockType::Type(_) => push!(1), + BlockType::FuncType(id) => { + let index = SignatureIndex::from_u32(*id); + let Some(ty) = self.sigs.get(&index) else { + bail!("missing type for func {}", id.red()) + }; + ins_and_outs!(ty); + } + } + }}; + } + + let mut scopes = vec![stack]; + + for op in &self.code { + #[rustfmt::skip] + match op { + Block { blockty } => { + block_type!(blockty); // we'll say any return slots have been pre-allocated + scopes.push(stack); + } + Loop { blockty } => { + block_type!(blockty); // return slots + scopes.push(stack); + } + If { blockty } => { + pop!(); // pop the conditional + block_type!(blockty); // return slots + scopes.push(stack); + } + Else => { + stack = match scopes.last() { + Some(scope) => *scope, + None => bail!("malformed if-else scope"), + }; + } + End => { + stack = match scopes.pop() { + Some(stack) => stack, + None => bail!("malformed scoping detected at end of block"), + }; + } + + Call { function_index } => { + let index = FunctionIndex::from_u32(*function_index); + let Some(ty) = self.funcs.get(&index) else { + bail!("missing type for func {}", function_index.red()) + }; + ins_and_outs!(ty) + } + CallIndirect { type_index, .. } => { + let index = SignatureIndex::from_u32(*type_index); + let Some(ty) = self.sigs.get(&index) else { + bail!("missing type for signature {}", type_index.red()) + }; + ins_and_outs!(ty); + pop!() // the table index + } + + MemoryFill { .. } => ins_and_outs!(InternalFunc::MemoryFill.ty()), + MemoryCopy { .. } => ins_and_outs!(InternalFunc::MemoryCopy.ty()), + + op!( + Nop, Unreachable, + I32Eqz, I64Eqz, I32Clz, I32Ctz, I32Popcnt, I64Clz, I64Ctz, I64Popcnt, + ) + | dot!( + Br, Return, + LocalTee, MemoryGrow, + I32Load, I64Load, F32Load, F64Load, + I32Load8S, I32Load8U, I32Load16S, I32Load16U, I64Load8S, I64Load8U, + I64Load16S, I64Load16U, I64Load32S, I64Load32U, + I32WrapI64, I64ExtendI32S, I64ExtendI32U, + I32Extend8S, I32Extend16S, I64Extend8S, I64Extend16S, I64Extend32S, + F32Abs, F32Neg, F32Ceil, F32Floor, F32Trunc, F32Nearest, F32Sqrt, + F64Abs, F64Neg, F64Ceil, F64Floor, F64Trunc, F64Nearest, F64Sqrt, + I32TruncF32S, I32TruncF32U, I32TruncF64S, I32TruncF64U, + I64TruncF32S, I64TruncF32U, I64TruncF64S, I64TruncF64U, + F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64, + F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32, + I32ReinterpretF32, I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U, + I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, + ) => {} + + dot!( + LocalGet, GlobalGet, MemorySize, + I32Const, I64Const, F32Const, F64Const, + ) => push!(), + + op!( + Drop, + I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU, + I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU, + F32Eq, F32Ne, F32Lt, F32Gt, F32Le, F32Ge, + F64Eq, F64Ne, F64Lt, F64Gt, F64Le, F64Ge, + I32Add, I32Sub, I32Mul, I32DivS, I32DivU, I32RemS, I32RemU, + I64Add, I64Sub, I64Mul, I64DivS, I64DivU, I64RemS, I64RemU, + I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr, + I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr, + F32Add, F32Sub, F32Mul, F32Div, F32Min, F32Max, F32Copysign, + F64Add, F64Sub, F64Mul, F64Div, F64Min, F64Max, F64Copysign, + ) + | dot!(BrIf, BrTable, LocalSet, GlobalSet) => pop!(), + + dot!( + Select, + I32Store, I64Store, F32Store, F64Store, I32Store8, I32Store16, I64Store8, I64Store16, I64Store32, + ) => pop!(2), + + unsupported @ dot!(Try, Catch, Throw, Rethrow, ThrowRef, TryTable) => { + bail!("exception-handling extension not supported {unsupported:?}") + }, + + unsupported @ dot!(ReturnCall, ReturnCallIndirect) => { + bail!("tail-call extension not supported {unsupported:?}") + } + + unsupported @ dot!(CallRef, ReturnCallRef) => { + bail!("typed function references extension not supported {unsupported:?}") + } + + unsupported @ (dot!(Delegate) | op!(CatchAll)) => { + bail!("exception-handling extension not supported {unsupported:?}") + }, + + unsupported @ (op!(RefIsNull) | dot!(TypedSelect, RefNull, RefFunc, RefEq)) => { + bail!("reference-types extension not supported {unsupported:?}") + }, + + unsupported @ dot!(RefAsNonNull, BrOnNull, BrOnNonNull) => { + bail!("typed function references extension not supported {unsupported:?}") + }, + + unsupported @ ( + dot!( + MemoryInit, DataDrop, TableInit, ElemDrop, + TableCopy, TableFill, TableGet, TableSet, TableGrow, TableSize + ) + ) => bail!("bulk-memory-operations extension not fully supported {unsupported:?}"), + + unsupported @ dot!(MemoryDiscard) => { + bail!("typed function references extension not supported {unsupported:?}") + } + + unsupported @ ( + dot!( + StructNew, StructNewDefault, StructGet, StructGetS, StructGetU, StructSet, + ArrayNew, ArrayNewDefault, ArrayNewFixed, ArrayNewData, ArrayNewElem, + ArrayGet, ArrayGetS, ArrayGetU, ArraySet, ArrayLen, ArrayFill, ArrayCopy, + ArrayInitData, ArrayInitElem, + RefTestNonNull, RefTestNullable, RefCastNonNull, RefCastNullable, + BrOnCast, BrOnCastFail, AnyConvertExtern, ExternConvertAny, RefI31, I31GetS, I31GetU + ) + ) => bail!("garbage collection extension not supported {unsupported:?}"), + + unsupported @ ( + dot!( + MemoryAtomicNotify, MemoryAtomicWait32, MemoryAtomicWait64, AtomicFence, I32AtomicLoad, + I64AtomicLoad, I32AtomicLoad8U, I32AtomicLoad16U, I64AtomicLoad8U, I64AtomicLoad16U, + I64AtomicLoad32U, I32AtomicStore, I64AtomicStore, I32AtomicStore8, I32AtomicStore16, + I64AtomicStore8, I64AtomicStore16, I64AtomicStore32, I32AtomicRmwAdd, I64AtomicRmwAdd, + I32AtomicRmw8AddU, I32AtomicRmw16AddU, + I64AtomicRmw8AddU, I64AtomicRmw16AddU, I64AtomicRmw32AddU, + I32AtomicRmwSub, I64AtomicRmwSub, I32AtomicRmw8SubU, I32AtomicRmw16SubU, I64AtomicRmw8SubU, + I64AtomicRmw16SubU, I64AtomicRmw32SubU, I32AtomicRmwAnd, I64AtomicRmwAnd, I32AtomicRmw8AndU, + I32AtomicRmw16AndU, I64AtomicRmw8AndU, I64AtomicRmw16AndU, I64AtomicRmw32AndU, I32AtomicRmwOr, + I64AtomicRmwOr, I32AtomicRmw8OrU, I32AtomicRmw16OrU, I64AtomicRmw8OrU, I64AtomicRmw16OrU, + I64AtomicRmw32OrU, I32AtomicRmwXor, I64AtomicRmwXor, I32AtomicRmw8XorU, I32AtomicRmw16XorU, + I64AtomicRmw8XorU, I64AtomicRmw16XorU, I64AtomicRmw32XorU, I32AtomicRmwXchg, I64AtomicRmwXchg, + I32AtomicRmw8XchgU, I32AtomicRmw16XchgU, I64AtomicRmw8XchgU, I64AtomicRmw16XchgU, + I64AtomicRmw32XchgU, I32AtomicRmwCmpxchg, I64AtomicRmwCmpxchg, I32AtomicRmw8CmpxchgU, + I32AtomicRmw16CmpxchgU, I64AtomicRmw8CmpxchgU, I64AtomicRmw16CmpxchgU, I64AtomicRmw32CmpxchgU + ) + ) => bail!("threads extension not supported {unsupported:?}"), + + unsupported @ ( + dot!( + V128Load, V128Load8x8S, V128Load8x8U, V128Load16x4S, V128Load16x4U, V128Load32x2S, + V128Load8Splat, V128Load16Splat, V128Load32Splat, V128Load64Splat, V128Load32Zero, + V128Load64Zero, V128Load32x2U, + V128Store, V128Load8Lane, V128Load16Lane, V128Load32Lane, V128Load64Lane, V128Store8Lane, + V128Store16Lane, V128Store32Lane, V128Store64Lane, V128Const, + I8x16Shuffle, I8x16ExtractLaneS, I8x16ExtractLaneU, I8x16ReplaceLane, I16x8ExtractLaneS, + I16x8ExtractLaneU, I16x8ReplaceLane, I32x4ExtractLane, I32x4ReplaceLane, I64x2ExtractLane, + I64x2ReplaceLane, F32x4ExtractLane, F32x4ReplaceLane, F64x2ExtractLane, F64x2ReplaceLane, + I8x16Swizzle, I8x16Splat, I16x8Splat, I32x4Splat, I64x2Splat, F32x4Splat, F64x2Splat, I8x16Eq, + I8x16Ne, I8x16LtS, I8x16LtU, I8x16GtS, I8x16GtU, I8x16LeS, I8x16LeU, I8x16GeS, I8x16GeU, + I16x8Eq, I16x8Ne, I16x8LtS, I16x8LtU, I16x8GtS, I16x8GtU, I16x8LeS, I16x8LeU, I16x8GeS, + I16x8GeU, I32x4Eq, I32x4Ne, I32x4LtS, I32x4LtU, I32x4GtS, I32x4GtU, I32x4LeS, I32x4LeU, + I32x4GeS, I32x4GeU, I64x2Eq, I64x2Ne, I64x2LtS, I64x2GtS, I64x2LeS, I64x2GeS, + F32x4Eq, F32x4Ne, F32x4Lt, F32x4Gt, F32x4Le, F32x4Ge, + F64x2Eq, F64x2Ne, F64x2Lt, F64x2Gt, F64x2Le, F64x2Ge, + V128Not, V128And, V128AndNot, V128Or, V128Xor, V128Bitselect, V128AnyTrue, + I8x16Abs, I8x16Neg, I8x16Popcnt, I8x16AllTrue, I8x16Bitmask, + I8x16NarrowI16x8S, I8x16NarrowI16x8U, + I8x16Shl, I8x16ShrS, I8x16ShrU, I8x16Add, I8x16AddSatS, I8x16AddSatU, I8x16Sub, I8x16SubSatS, + I8x16SubSatU, I8x16MinS, I8x16MinU, I8x16MaxS, I8x16MaxU, I8x16AvgrU, + I16x8ExtAddPairwiseI8x16S, I16x8ExtAddPairwiseI8x16U, I16x8Abs, I16x8Neg, I16x8Q15MulrSatS, + I16x8AllTrue, I16x8Bitmask, I16x8NarrowI32x4S, I16x8NarrowI32x4U, I16x8ExtendLowI8x16S, + I16x8ExtendHighI8x16S, I16x8ExtendLowI8x16U, I16x8ExtendHighI8x16U, + I16x8Shl, I16x8ShrS, I16x8ShrU, I16x8Add, I16x8AddSatS, I16x8AddSatU, + I16x8Sub, I16x8SubSatS, I16x8SubSatU, I16x8Mul, I16x8MinS, I16x8MinU, + I16x8MaxS, I16x8MaxU, I16x8AvgrU, I16x8ExtMulLowI8x16S, + I16x8ExtMulHighI8x16S, I16x8ExtMulLowI8x16U, I16x8ExtMulHighI8x16U, + I32x4ExtAddPairwiseI16x8U, I32x4Abs, I32x4Neg, I32x4AllTrue, I32x4Bitmask, + I32x4ExtAddPairwiseI16x8S, I32x4ExtendLowI16x8S, I32x4ExtendHighI16x8S, I32x4ExtendLowI16x8U, + I32x4ExtendHighI16x8U, I32x4Shl, I32x4ShrS, I32x4ShrU, I32x4Add, I32x4Sub, I32x4Mul, + I32x4MinS, I32x4MinU, I32x4MaxS, I32x4MaxU, I32x4DotI16x8S, + I32x4ExtMulLowI16x8S, I32x4ExtMulHighI16x8S, I32x4ExtMulLowI16x8U, I32x4ExtMulHighI16x8U, + I64x2Abs, I64x2Neg, I64x2AllTrue, I64x2Bitmask, I64x2ExtendLowI32x4S, I64x2ExtendHighI32x4S, + I64x2ExtendLowI32x4U, I64x2ExtendHighI32x4U, I64x2Shl, I64x2ShrS, I64x2ShrU, I64x2Add, + I64x2ExtMulLowI32x4S, I64x2ExtMulHighI32x4S, I64x2Sub, I64x2Mul, + I64x2ExtMulLowI32x4U, I64x2ExtMulHighI32x4U, F32x4Ceil, F32x4Floor, F32x4Trunc, + F32x4Nearest, F32x4Abs, F32x4Neg, F32x4Sqrt, F32x4Add, F32x4Sub, F32x4Mul, F32x4Div, + F32x4Min, F32x4Max, F32x4PMin, F32x4PMax, F64x2Ceil, F64x2Floor, F64x2Trunc, + F64x2Nearest, F64x2Abs, F64x2Neg, F64x2Sqrt, F64x2Add, F64x2Sub, F64x2Mul, F64x2Div, F64x2Min, + F64x2Max, F64x2PMin, F64x2PMax, I32x4TruncSatF32x4S, I32x4TruncSatF32x4U, F32x4ConvertI32x4S, + F32x4ConvertI32x4U, I32x4TruncSatF64x2SZero, I32x4TruncSatF64x2UZero, F64x2ConvertLowI32x4S, + F64x2ConvertLowI32x4U, F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4, I8x16RelaxedSwizzle, + I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, I32x4RelaxedTruncF64x2SZero, + I32x4RelaxedTruncF64x2UZero, F32x4RelaxedMadd, F32x4RelaxedNmadd, F64x2RelaxedMadd, + F64x2RelaxedNmadd, I8x16RelaxedLaneselect, I16x8RelaxedLaneselect, I32x4RelaxedLaneselect, + I64x2RelaxedLaneselect, F32x4RelaxedMin, F32x4RelaxedMax, F64x2RelaxedMin, F64x2RelaxedMax, + I16x8RelaxedQ15mulrS, I16x8RelaxedDotI8x16I7x16S, I32x4RelaxedDotI8x16I7x16AddS + ) + ) => bail!("SIMD extension not supported {unsupported:?}"), + }; + } + + if self.locals.is_none() { + bail!("missing locals info for func {}", self.func.as_u32().red()) + } + + let contention = worst; + if contention > self.frame_contention.into() { + bail!( + "too many values on the stack at once in func {}: {} > {}", + self.func.as_u32().red(), + contention.red(), + self.frame_contention.red() + ); + } + + let locals = self.locals.unwrap_or_default(); + Ok(worst + locals as u32 + 4) + } +} + +/// Note: implementers may panic if uninstrumented +pub trait DepthCheckedMachine { + fn stack_left(&mut self) -> u32; + fn set_stack(&mut self, size: u32); +} + +impl DepthCheckedMachine for Machine { + fn stack_left(&mut self) -> u32 { + let global = self.get_global(STYLUS_STACK_LEFT).unwrap(); + global.try_into().expect("instrumentation type mismatch") + } + + fn set_stack(&mut self, size: u32) { + self.set_global(STYLUS_STACK_LEFT, size.into()).unwrap(); + } +} diff --git a/arbitrator/prover/src/programs/dynamic.rs b/arbitrator/prover/src/programs/dynamic.rs new file mode 100644 index 000000000..36f49af85 --- /dev/null +++ b/arbitrator/prover/src/programs/dynamic.rs @@ -0,0 +1,154 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use super::{ + config::CompilePricingParams, + meter::{STYLUS_INK_LEFT, STYLUS_INK_STATUS}, + FuncMiddleware, Middleware, ModuleMod, +}; +use eyre::{bail, Result}; +use parking_lot::RwLock; +use wasmer_types::{GlobalIndex, GlobalInit, LocalFunctionIndex, Type}; +use wasmparser::{BlockType, Operator}; + +pub const SCRATCH_GLOBAL: &str = "stylus_scratch_global"; + +#[derive(Debug)] +pub struct DynamicMeter { + memory_fill: u64, + memory_copy: u64, + globals: RwLock>, +} + +impl DynamicMeter { + pub fn new(pricing: &CompilePricingParams) -> Self { + Self { + memory_fill: pricing.memory_fill_ink, + memory_copy: pricing.memory_copy_ink, + globals: RwLock::default(), + } + } +} + +impl Middleware for DynamicMeter { + type FM<'a> = FuncDynamicMeter; + + fn update_module(&self, module: &mut M) -> Result<()> { + let ink = module.get_global(STYLUS_INK_LEFT)?; + let status = module.get_global(STYLUS_INK_STATUS)?; + let scratch = module.add_global(SCRATCH_GLOBAL, Type::I32, GlobalInit::I32Const(0))?; + *self.globals.write() = Some([ink, status, scratch]); + Ok(()) + } + + fn instrument<'a>(&self, _: LocalFunctionIndex) -> Result> { + let globals = self.globals.read().expect("no globals"); + Ok(FuncDynamicMeter::new( + self.memory_fill, + self.memory_copy, + globals, + )) + } + + fn name(&self) -> &'static str { + "dynamic ink meter" + } +} + +#[derive(Debug)] +pub struct FuncDynamicMeter { + memory_fill: u64, + memory_copy: u64, + globals: [GlobalIndex; 3], +} + +impl FuncDynamicMeter { + fn new(memory_fill: u64, memory_copy: u64, globals: [GlobalIndex; 3]) -> Self { + Self { + memory_fill, + memory_copy, + globals, + } + } +} + +impl<'a> FuncMiddleware<'a> for FuncDynamicMeter { + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>, + { + use Operator::*; + macro_rules! dot { + ($first:ident $(,$opcode:ident)* $(,)?) => { + $first { .. } $(| $opcode { .. })* + }; + } + macro_rules! get { + ($global:expr) => { + GlobalGet { + global_index: $global, + } + }; + } + macro_rules! set { + ($global:expr) => { + GlobalSet { + global_index: $global, + } + }; + } + + let [ink, status, scratch] = self.globals.map(|x| x.as_u32()); + let blockty = BlockType::Empty; + + #[rustfmt::skip] + let linear = |coefficient| { + [ + // [user] → move user value to scratch + set!(scratch), + get!(ink), + get!(ink), + get!(scratch), + + // [ink ink size] → cost = size * coefficient (can't overflow) + I64ExtendI32U, + I64Const { value: coefficient }, + I64Mul, + + // [ink ink cost] → ink -= cost + I64Sub, + set!(ink), + get!(ink), + + // [old_ink, new_ink] → (old_ink < new_ink) (overflow detected) + I64LtU, + If { blockty }, + I32Const { value: 1 }, + set!(status), + Unreachable, + End, + + // [] → resume since user paid for ink + get!(scratch), + ] + }; + + match op { + dot!(MemoryFill) => out.extend(linear(self.memory_fill as i64)), + dot!(MemoryCopy) => out.extend(linear(self.memory_copy as i64)), + dot!( + MemoryInit, DataDrop, ElemDrop, TableInit, TableCopy, TableFill, TableGet, + TableSet, TableGrow, TableSize + ) => { + bail!("opcode not supported") + } + _ => {} + } + out.extend([op]); + Ok(()) + } + + fn name(&self) -> &'static str { + "dynamic ink meter" + } +} diff --git a/arbitrator/prover/src/programs/heap.rs b/arbitrator/prover/src/programs/heap.rs new file mode 100644 index 000000000..310405ec9 --- /dev/null +++ b/arbitrator/prover/src/programs/heap.rs @@ -0,0 +1,117 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::value::{ArbValueType, FunctionType}; + +use super::{ + config::CompileMemoryParams, dynamic::SCRATCH_GLOBAL, FuncMiddleware, Middleware, ModuleMod, +}; +use arbutil::Color; +use eyre::{bail, Result}; +use parking_lot::RwLock; +use wasmer_types::{FunctionIndex, GlobalIndex, ImportIndex, LocalFunctionIndex, Pages}; +use wasmparser::Operator; + +#[derive(Debug)] +pub struct HeapBound { + /// Upper bounds the amount of heap memory a module may use + limit: Pages, + /// Import called when allocating new pages + pay_func: RwLock>, + /// Scratch global shared among middlewares + scratch: RwLock>, +} + +impl HeapBound { + const PAY_FUNC: &'static str = "pay_for_memory_grow"; + + pub fn new(bounds: CompileMemoryParams) -> Self { + Self { + limit: bounds.heap_bound, + pay_func: RwLock::default(), + scratch: RwLock::default(), + } + } +} + +impl Middleware for HeapBound { + type FM<'a> = FuncHeapBound; + + fn update_module(&self, module: &mut M) -> Result<()> { + let scratch = module.get_global(SCRATCH_GLOBAL)?; + *self.scratch.write() = Some(scratch); + + let memory = module.memory_info()?; + let min = memory.min; + let max = memory.max; + let lim = self.limit; + + if min > lim { + bail!("memory size {} exceeds bound {}", min.0.red(), lim.0.red()); + } + if max == Some(min) { + return Ok(()); + } + + let ImportIndex::Function(import) = module.get_import("vm_hooks", Self::PAY_FUNC)? else { + bail!("wrong import kind for {}", Self::PAY_FUNC.red()); + }; + + let ty = module.get_function(import)?; + if ty != FunctionType::new(vec![ArbValueType::I32], vec![]) { + bail!("wrong type for {}: {}", Self::PAY_FUNC.red(), ty.red()); + } + + *self.pay_func.write() = Some(import); + Ok(()) + } + + fn instrument<'a>(&self, _: LocalFunctionIndex) -> Result> { + Ok(FuncHeapBound { + scratch: self.scratch.read().expect("no scratch global"), + pay_func: *self.pay_func.read(), + }) + } + + fn name(&self) -> &'static str { + "heap bound" + } +} + +#[derive(Debug)] +pub struct FuncHeapBound { + pay_func: Option, + scratch: GlobalIndex, +} + +impl<'a> FuncMiddleware<'a> for FuncHeapBound { + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>, + { + use Operator::*; + + let Some(pay_func) = self.pay_func else { + out.extend([op]); + return Ok(()); + }; + + let global_index = self.scratch.as_u32(); + let function_index = pay_func.as_u32(); + + if let MemoryGrow { .. } = op { + out.extend([ + GlobalSet { global_index }, + GlobalGet { global_index }, + GlobalGet { global_index }, + Call { function_index }, + ]); + } + out.extend([op]); + Ok(()) + } + + fn name(&self) -> &'static str { + "heap bound" + } +} diff --git a/arbitrator/prover/src/programs/memory.rs b/arbitrator/prover/src/programs/memory.rs new file mode 100644 index 000000000..7253b59dc --- /dev/null +++ b/arbitrator/prover/src/programs/memory.rs @@ -0,0 +1,125 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct MemoryModel { + /// Number of pages a tx gets for free + pub free_pages: u16, + /// Base cost of each additional wasm page + pub page_gas: u16, +} + +impl Default for MemoryModel { + fn default() -> Self { + Self { + free_pages: u16::MAX, + page_gas: 0, + } + } +} + +impl MemoryModel { + pub const fn new(free_pages: u16, page_gas: u16) -> Self { + Self { + free_pages, + page_gas, + } + } + + /// Determines the gas cost of allocating `new` pages given `open` are active and `ever` have ever been. + pub fn gas_cost(&self, new: u16, open: u16, ever: u16) -> u64 { + let new_open = open.saturating_add(new); + let new_ever = ever.max(new_open); + + // free until expansion beyond the first few + if new_ever <= self.free_pages { + return 0; + } + + let credit = |pages: u16| pages.saturating_sub(self.free_pages); + let adding = credit(new_open).saturating_sub(credit(open)) as u64; + let linear = adding.saturating_mul(self.page_gas.into()); + let expand = Self::exp(new_ever) - Self::exp(ever); + linear.saturating_add(expand) + } + + fn exp(pages: u16) -> u64 { + MEMORY_EXPONENTS + .get(pages as usize) + .map(|&x| x.into()) + .unwrap_or(u64::MAX) + } +} + +const MEMORY_EXPONENTS: [u32; 129] = [ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 14, 17, 19, 22, 25, 29, 33, 38, + 43, 50, 57, 65, 75, 85, 98, 112, 128, 147, 168, 193, 221, 253, 289, 331, 379, 434, 497, 569, + 651, 745, 853, 976, 1117, 1279, 1463, 1675, 1917, 2194, 2511, 2874, 3290, 3765, 4309, 4932, + 5645, 6461, 7395, 8464, 9687, 11087, 12689, 14523, 16621, 19024, 21773, 24919, 28521, 32642, + 37359, 42758, 48938, 56010, 64104, 73368, 83971, 96106, 109994, 125890, 144082, 164904, 188735, + 216010, 247226, 282953, 323844, 370643, 424206, 485509, 555672, 635973, 727880, 833067, 953456, + 1091243, 1248941, 1429429, 1636000, 1872423, 2143012, 2452704, 2807151, 3212820, 3677113, + 4208502, 4816684, 5512756, 6309419, 7221210, 8264766, 9459129, 10826093, 12390601, 14181199, + 16230562, 18576084, 21260563, 24332984, 27849408, 31873999, +]; + +#[test] +fn test_tables() { + let base = f64::exp(31_874_000_f64.ln() / 128.); + + for pages in 0..129 { + let value = base.powi(pages.into()) as u64; + assert_eq!(value, MemoryModel::exp(pages)); + } + assert_eq!(u64::MAX, MemoryModel::exp(129)); + assert_eq!(u64::MAX, MemoryModel::exp(u16::MAX)); +} + +#[test] +fn test_model() { + let model = MemoryModel::new(2, 1000); + + for jump in 1..=128 { + let mut total = 0; + let mut pages = 0; + while pages < 128 { + let jump = jump.min(128 - pages); + total += model.gas_cost(jump, pages, pages); + pages += jump; + } + assert_eq!(total, 31999998); + } + + for jump in 1..=128 { + let mut total = 0; + let mut open = 0; + let mut ever = 0; + let mut adds = 0; + while ever < 128 { + let jump = jump.min(128 - open); + total += model.gas_cost(jump, open, ever); + open += jump; + ever = ever.max(open); + + if ever > model.free_pages { + adds += jump.min(ever - model.free_pages) as u64; + } + + // pretend we've deallocated some pages + open -= jump / 2; + } + let expected = 31873998 + adds * model.page_gas as u64; + assert_eq!(total, expected); + } + + // check saturation + assert_eq!(u64::MAX, model.gas_cost(129, 0, 0)); + assert_eq!(u64::MAX, model.gas_cost(u16::MAX, 0, 0)); + + // check free pages + let model = MemoryModel::new(128, 1000); + assert_eq!(0, model.gas_cost(128, 0, 0)); + assert_eq!(0, model.gas_cost(128, 0, 128)); + assert_eq!(u64::MAX, model.gas_cost(129, 0, 0)); +} diff --git a/arbitrator/prover/src/programs/meter.rs b/arbitrator/prover/src/programs/meter.rs new file mode 100644 index 000000000..ab069fd91 --- /dev/null +++ b/arbitrator/prover/src/programs/meter.rs @@ -0,0 +1,532 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::{ + programs::{ + config::{CompilePricingParams, PricingParams, SigMap}, + FuncMiddleware, Middleware, ModuleMod, + }, + value::FunctionType, + Machine, +}; +use arbutil::{evm, operator::OperatorInfo, Bytes32}; +use derivative::Derivative; +use eyre::Result; +use fnv::FnvHashMap as HashMap; +use parking_lot::RwLock; +use std::{ + fmt::{Debug, Display}, + sync::Arc, +}; +use wasmer_types::{GlobalIndex, GlobalInit, LocalFunctionIndex, SignatureIndex, Type}; +use wasmparser::{BlockType, Operator}; + +use super::config::OpCosts; + +pub const STYLUS_INK_LEFT: &str = "stylus_ink_left"; +pub const STYLUS_INK_STATUS: &str = "stylus_ink_status"; + +pub trait OpcodePricer: Fn(&Operator, &SigMap) -> u64 + Send + Sync + Clone {} + +impl OpcodePricer for T where T: Fn(&Operator, &SigMap) -> u64 + Send + Sync + Clone {} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Meter { + /// Associates opcodes to their ink costs. + #[derivative(Debug = "ignore")] + costs: F, + /// Cost of checking the amount of ink left. + header_cost: u64, + /// Ink and ink status globals. + globals: RwLock>, + /// The types of the module being instrumented + sigs: RwLock>>, +} + +impl Meter { + pub fn new(pricing: &CompilePricingParams) -> Meter { + Self { + costs: pricing.costs, + header_cost: pricing.ink_header_cost, + globals: RwLock::default(), + sigs: RwLock::default(), + } + } +} + +impl Meter { + pub fn globals(&self) -> [GlobalIndex; 2] { + self.globals.read().expect("missing globals") + } +} + +impl Middleware for Meter +where + M: ModuleMod, + F: OpcodePricer + 'static, +{ + type FM<'a> = FuncMeter<'a, F>; + + fn update_module(&self, module: &mut M) -> Result<()> { + let start_status = GlobalInit::I32Const(0); + let ink = module.add_global(STYLUS_INK_LEFT, Type::I64, GlobalInit::I64Const(0))?; + let status = module.add_global(STYLUS_INK_STATUS, Type::I32, start_status)?; + *self.globals.write() = Some([ink, status]); + *self.sigs.write() = Some(Arc::new(module.all_signatures()?)); + Ok(()) + } + + fn instrument<'a>(&self, _: LocalFunctionIndex) -> Result> { + let [ink, status] = self.globals(); + let sigs = self.sigs.read(); + let sigs = sigs.as_ref().expect("no types"); + Ok(FuncMeter::new( + ink, + status, + self.costs.clone(), + self.header_cost, + sigs.clone(), + )) + } + + fn name(&self) -> &'static str { + "ink meter" + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct FuncMeter<'a, F: OpcodePricer> { + /// Represents the amount of ink left for consumption. + ink_global: GlobalIndex, + /// Represents whether the machine is out of ink. + status_global: GlobalIndex, + /// Instructions of the current basic block. + block: Vec>, + /// The accumulated cost of the current basic block. + block_cost: u64, + /// Cost of checking the amount of ink left. + header_cost: u64, + /// Associates opcodes to their ink costs. + #[derivative(Debug = "ignore")] + costs: F, + /// The types of the module being instrumented. + sigs: Arc, +} + +impl<'a, F: OpcodePricer> FuncMeter<'a, F> { + fn new( + ink_global: GlobalIndex, + status_global: GlobalIndex, + costs: F, + header_cost: u64, + sigs: Arc, + ) -> Self { + Self { + ink_global, + status_global, + block: vec![], + block_cost: 0, + header_cost, + costs, + sigs, + } + } +} + +impl<'a, F: OpcodePricer> FuncMiddleware<'a> for FuncMeter<'a, F> { + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>, + { + use Operator::*; + + let end = op.ends_basic_block(); + + let op_cost = (self.costs)(&op, &self.sigs); + let mut cost = self.block_cost.saturating_add(op_cost); + self.block_cost = cost; + self.block.push(op); + + if end { + let ink = self.ink_global.as_u32(); + let status = self.status_global.as_u32(); + let blockty = BlockType::Empty; + + // include the cost of executing the header + cost = cost.saturating_add(self.header_cost); + + out.extend([ + // if ink < cost => panic with status = 1 + GlobalGet { global_index: ink }, + I64Const { value: cost as i64 }, + I64LtU, + If { blockty }, + I32Const { value: 1 }, + GlobalSet { + global_index: status, + }, + Unreachable, + End, + // ink -= cost + GlobalGet { global_index: ink }, + I64Const { value: cost as i64 }, + I64Sub, + GlobalSet { global_index: ink }, + ]); + out.extend(self.block.drain(..)); + self.block_cost = 0; + } + Ok(()) + } + + fn name(&self) -> &'static str { + "ink meter" + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MachineMeter { + Ready(u64), + Exhausted, +} + +impl MachineMeter { + pub fn ink(self) -> u64 { + match self { + Self::Ready(ink) => ink, + Self::Exhausted => 0, + } + } + + pub fn status(self) -> u32 { + match self { + Self::Ready(_) => 0, + Self::Exhausted => 1, + } + } +} + +/// We don't implement `From` since it's unclear what 0 would map to +#[allow(clippy::from_over_into)] +impl Into for MachineMeter { + fn into(self) -> u64 { + self.ink() + } +} + +impl Display for MachineMeter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ready(ink) => write!(f, "{ink} ink"), + Self::Exhausted => write!(f, "exhausted"), + } + } +} + +#[derive(Debug)] +pub struct OutOfInkError; + +impl std::error::Error for OutOfInkError {} + +impl Display for OutOfInkError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "out of ink") + } +} + +/// Note: implementers may panic if uninstrumented +pub trait MeteredMachine { + fn ink_left(&self) -> MachineMeter; + fn set_meter(&mut self, meter: MachineMeter); + + fn set_ink(&mut self, ink: u64) { + self.set_meter(MachineMeter::Ready(ink)); + } + + fn out_of_ink(&mut self) -> Result { + self.set_meter(MachineMeter::Exhausted); + Err(OutOfInkError) + } + + fn ink_ready(&mut self) -> Result { + let MachineMeter::Ready(ink_left) = self.ink_left() else { + return self.out_of_ink(); + }; + Ok(ink_left) + } + + fn buy_ink(&mut self, ink: u64) -> Result<(), OutOfInkError> { + let ink_left = self.ink_ready()?; + if ink_left < ink { + return self.out_of_ink(); + } + self.set_ink(ink_left - ink); + Ok(()) + } + + /// Checks if the user has enough ink, but doesn't burn any + fn require_ink(&mut self, ink: u64) -> Result<(), OutOfInkError> { + let ink_left = self.ink_ready()?; + if ink_left < ink { + return self.out_of_ink(); + } + Ok(()) + } + + /// Pays for a write into the client. + fn pay_for_write(&mut self, bytes: u32) -> Result<(), OutOfInkError> { + self.buy_ink(sat_add_mul(5040, 30, bytes.saturating_sub(32))) + } + + /// Pays for a read into the host. + fn pay_for_read(&mut self, bytes: u32) -> Result<(), OutOfInkError> { + self.buy_ink(sat_add_mul(16381, 55, bytes.saturating_sub(32))) + } + + /// Pays for both I/O and keccak. + fn pay_for_keccak(&mut self, bytes: u32) -> Result<(), OutOfInkError> { + let words = evm::evm_words(bytes).saturating_sub(2); + self.buy_ink(sat_add_mul(121800, 21000, words)) + } + + /// Pays for copying bytes from geth. + fn pay_for_geth_bytes(&mut self, bytes: u32) -> Result<(), OutOfInkError> { + self.pay_for_read(bytes) // TODO: determine value + } + + /// Pays for the variable costs of exponentiation. + fn pay_for_pow(&mut self, exponent: &Bytes32) -> Result<(), OutOfInkError> { + let mut exp = 33; + for byte in exponent.iter() { + match *byte == 0 { + true => exp -= 1, // reduce cost for each big-endian 0 byte + false => break, + } + } + self.buy_ink(3000 + exp * 17500) + } +} + +pub trait GasMeteredMachine: MeteredMachine { + fn pricing(&self) -> PricingParams; + + fn gas_left(&self) -> Result { + let pricing = self.pricing(); + match self.ink_left() { + MachineMeter::Ready(ink) => Ok(pricing.ink_to_gas(ink)), + MachineMeter::Exhausted => Err(OutOfInkError), + } + } + + fn buy_gas(&mut self, gas: u64) -> Result<(), OutOfInkError> { + let pricing = self.pricing(); + self.buy_ink(pricing.gas_to_ink(gas)) + } + + /// Checks if the user has enough gas, but doesn't burn any + fn require_gas(&mut self, gas: u64) -> Result<(), OutOfInkError> { + let pricing = self.pricing(); + self.require_ink(pricing.gas_to_ink(gas)) + } + + fn pay_for_evm_log(&mut self, topics: u32, data_len: u32) -> Result<(), OutOfInkError> { + let cost = (1 + topics as u64) * evm::LOG_TOPIC_GAS; + let cost = cost.saturating_add(data_len as u64 * evm::LOG_DATA_GAS); + self.buy_gas(cost) + } +} + +fn sat_add_mul(base: u64, per: u64, count: u32) -> u64 { + base.saturating_add(per.saturating_mul(count.into())) +} + +impl MeteredMachine for Machine { + fn ink_left(&self) -> MachineMeter { + macro_rules! convert { + ($global:expr) => {{ + $global.unwrap().try_into().expect("type mismatch") + }}; + } + + let ink = || convert!(self.get_global(STYLUS_INK_LEFT)); + let status: u32 = convert!(self.get_global(STYLUS_INK_STATUS)); + + match status { + 0 => MachineMeter::Ready(ink()), + _ => MachineMeter::Exhausted, + } + } + + fn set_meter(&mut self, meter: MachineMeter) { + let ink = meter.ink(); + let status = meter.status(); + self.set_global(STYLUS_INK_LEFT, ink.into()).unwrap(); + self.set_global(STYLUS_INK_STATUS, status.into()).unwrap(); + } +} + +pub fn pricing_v1(op: &Operator, tys: &HashMap) -> u64 { + use Operator::*; + + macro_rules! op { + ($first:ident $(,$opcode:ident)*) => { + $first $(| $opcode)* + }; + } + macro_rules! dot { + ($first:ident $(,$opcode:ident)*) => { + $first { .. } $(| $opcode { .. })* + }; + } + + #[rustfmt::skip] + let ink = match op { + op!(Unreachable, Return) => 1, + op!(Nop) | dot!(I32Const, I64Const) => 1, + + op!(Drop) => 9, // could be 1, but using a higher number helps limit the number of ops in BOLD + + dot!(Block, Loop) | op!(Else, End) => 1, + dot!(Br, BrIf, If) => 765, + dot!(Select) => 1250, // TODO: improve wasmer codegen + dot!(Call) => 3800, + dot!(LocalGet, LocalTee) => 75, + dot!(LocalSet) => 210, + dot!(GlobalGet) => 225, + dot!(GlobalSet) => 575, + dot!(I32Load, I32Load8S, I32Load8U, I32Load16S, I32Load16U) => 670, + dot!(I64Load, I64Load8S, I64Load8U, I64Load16S, I64Load16U, I64Load32S, I64Load32U) => 680, + dot!(I32Store, I32Store8, I32Store16) => 825, + dot!(I64Store, I64Store8, I64Store16, I64Store32) => 950, + dot!(MemorySize) => 3000, + dot!(MemoryGrow) => 8050, // rest of cost handled by memory pricer + + op!(I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU) => 170, + op!(I64Eqz, I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU) => 225, + + op!(I32Clz, I32Ctz) => 210, + op!(I32Add, I32Sub) => 70, + op!(I32Mul) => 160, + op!(I32DivS, I32DivU, I32RemS, I32RemU) => 1120, + op!(I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr) => 70, + + op!(I64Clz, I64Ctz) => 210, + op!(I64Add, I64Sub) => 100, + op!(I64Mul) => 160, + op!(I64DivS, I64DivU, I64RemS, I64RemU) => 1270, + op!(I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr) => 100, + + op!(I32Popcnt) => 2650, // slow on ARM, fast on x86 + op!(I64Popcnt) => 6000, // slow on ARM, fast on x86 + + op!(I32WrapI64, I64ExtendI32S, I64ExtendI32U) => 100, + op!(I32Extend8S, I32Extend16S, I64Extend8S, I64Extend16S, I64Extend32S) => 100, + dot!(MemoryCopy) => 950, + dot!(MemoryFill) => 950, + + BrTable { targets } => { + 2400 + 325 * targets.len() as u64 + }, + CallIndirect { type_index, .. } => { + let ty = tys.get(&SignatureIndex::from_u32(*type_index)).expect("no type"); + 13610 + 650 * ty.inputs.len() as u64 + }, + + // we don't support the following, so return u64::MAX + dot!( + Try, Catch, CatchAll, Delegate, Throw, Rethrow, ThrowRef, TryTable, + + RefNull, RefIsNull, RefFunc, RefEq, + + CallRef, ReturnCallRef, RefAsNonNull, BrOnNull, BrOnNonNull, + + TypedSelect, ReturnCall, ReturnCallIndirect, + + MemoryInit, DataDrop, TableInit, ElemDrop, + TableCopy, TableFill, TableGet, TableSet, TableGrow, TableSize, + + MemoryDiscard, + + StructNew, StructNewDefault, StructGet, StructGetS, StructGetU, StructSet, + ArrayNew, ArrayNewDefault, ArrayNewFixed, ArrayNewData, ArrayNewElem, + ArrayGet, ArrayGetS, ArrayGetU, ArraySet, ArrayLen, ArrayFill, ArrayCopy, + ArrayInitData, ArrayInitElem, RefTestNonNull, RefTestNullable, RefCastNonNull, RefCastNullable, + BrOnCast, BrOnCastFail, AnyConvertExtern, ExternConvertAny, RefI31, I31GetS, I31GetU, + + F32Load, F64Load, F32Store, F64Store, F32Const, F64Const, + F32Eq, F32Ne, F32Lt, F32Gt, F32Le, F32Ge, + F64Eq, F64Ne, F64Lt, F64Gt, F64Le, F64Ge, + F32Abs, F32Neg, F32Ceil, F32Floor, F32Trunc, F32Nearest, F32Sqrt, F32Add, F32Sub, F32Mul, + F32Div, F32Min, F32Max, F32Copysign, F64Abs, F64Neg, F64Ceil, F64Floor, F64Trunc, + F64Nearest, F64Sqrt, F64Add, F64Sub, F64Mul, F64Div, F64Min, F64Max, F64Copysign, + I32TruncF32S, I32TruncF32U, I32TruncF64S, I32TruncF64U, + I64TruncF32S, I64TruncF32U, I64TruncF64S, I64TruncF64U, + F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64, + F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32, + I32ReinterpretF32, I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U, + I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, + + MemoryAtomicNotify, MemoryAtomicWait32, MemoryAtomicWait64, AtomicFence, I32AtomicLoad, + I64AtomicLoad, I32AtomicLoad8U, I32AtomicLoad16U, I64AtomicLoad8U, I64AtomicLoad16U, + I64AtomicLoad32U, I32AtomicStore, I64AtomicStore, I32AtomicStore8, I32AtomicStore16, + I64AtomicStore8, I64AtomicStore16, I64AtomicStore32, I32AtomicRmwAdd, I64AtomicRmwAdd, + I32AtomicRmw8AddU, I32AtomicRmw16AddU, I64AtomicRmw8AddU, I64AtomicRmw16AddU, I64AtomicRmw32AddU, + I32AtomicRmwSub, I64AtomicRmwSub, I32AtomicRmw8SubU, I32AtomicRmw16SubU, I64AtomicRmw8SubU, + I64AtomicRmw16SubU, I64AtomicRmw32SubU, I32AtomicRmwAnd, I64AtomicRmwAnd, I32AtomicRmw8AndU, + I32AtomicRmw16AndU, I64AtomicRmw8AndU, I64AtomicRmw16AndU, I64AtomicRmw32AndU, I32AtomicRmwOr, + I64AtomicRmwOr, I32AtomicRmw8OrU, I32AtomicRmw16OrU, I64AtomicRmw8OrU, I64AtomicRmw16OrU, + I64AtomicRmw32OrU, I32AtomicRmwXor, I64AtomicRmwXor, I32AtomicRmw8XorU, I32AtomicRmw16XorU, + I64AtomicRmw8XorU, I64AtomicRmw16XorU, I64AtomicRmw32XorU, I32AtomicRmwXchg, I64AtomicRmwXchg, + I32AtomicRmw8XchgU, I32AtomicRmw16XchgU, I64AtomicRmw8XchgU, I64AtomicRmw16XchgU, + I64AtomicRmw32XchgU, I32AtomicRmwCmpxchg, I64AtomicRmwCmpxchg, I32AtomicRmw8CmpxchgU, + I32AtomicRmw16CmpxchgU, I64AtomicRmw8CmpxchgU, I64AtomicRmw16CmpxchgU, I64AtomicRmw32CmpxchgU, + + V128Load, V128Load8x8S, V128Load8x8U, V128Load16x4S, V128Load16x4U, V128Load32x2S, V128Load32x2U, + V128Load8Splat, V128Load16Splat, V128Load32Splat, V128Load64Splat, V128Load32Zero, V128Load64Zero, + V128Store, V128Load8Lane, V128Load16Lane, V128Load32Lane, V128Load64Lane, V128Store8Lane, + V128Store16Lane, V128Store32Lane, V128Store64Lane, V128Const, + I8x16Shuffle, I8x16ExtractLaneS, I8x16ExtractLaneU, I8x16ReplaceLane, I16x8ExtractLaneS, + I16x8ExtractLaneU, I16x8ReplaceLane, I32x4ExtractLane, I32x4ReplaceLane, I64x2ExtractLane, + I64x2ReplaceLane, F32x4ExtractLane, F32x4ReplaceLane, F64x2ExtractLane, F64x2ReplaceLane, + I8x16Swizzle, I8x16Splat, I16x8Splat, I32x4Splat, I64x2Splat, F32x4Splat, F64x2Splat, I8x16Eq, + I8x16Ne, I8x16LtS, I8x16LtU, I8x16GtS, I8x16GtU, I8x16LeS, I8x16LeU, I8x16GeS, I8x16GeU, I16x8Eq, + I16x8Ne, I16x8LtS, I16x8LtU, I16x8GtS, I16x8GtU, I16x8LeS, I16x8LeU, I16x8GeS, I16x8GeU, I32x4Eq, + I32x4Ne, I32x4LtS, I32x4LtU, I32x4GtS, I32x4GtU, I32x4LeS, I32x4LeU, I32x4GeS, I32x4GeU, I64x2Eq, + I64x2Ne, I64x2LtS, I64x2GtS, I64x2LeS, I64x2GeS, + F32x4Eq, F32x4Ne, F32x4Lt, F32x4Gt, F32x4Le, F32x4Ge, + F64x2Eq, F64x2Ne, F64x2Lt, F64x2Gt, F64x2Le, F64x2Ge, + V128Not, V128And, V128AndNot, V128Or, V128Xor, V128Bitselect, V128AnyTrue, + I8x16Abs, I8x16Neg, I8x16Popcnt, I8x16AllTrue, I8x16Bitmask, I8x16NarrowI16x8S, I8x16NarrowI16x8U, + I8x16Shl, I8x16ShrS, I8x16ShrU, I8x16Add, I8x16AddSatS, I8x16AddSatU, I8x16Sub, I8x16SubSatS, + I8x16SubSatU, I8x16MinS, I8x16MinU, I8x16MaxS, I8x16MaxU, I8x16AvgrU, + I16x8ExtAddPairwiseI8x16S, I16x8ExtAddPairwiseI8x16U, I16x8Abs, I16x8Neg, I16x8Q15MulrSatS, + I16x8AllTrue, I16x8Bitmask, I16x8NarrowI32x4S, I16x8NarrowI32x4U, I16x8ExtendLowI8x16S, + I16x8ExtendHighI8x16S, I16x8ExtendLowI8x16U, I16x8ExtendHighI8x16U, I16x8Shl, I16x8ShrS, I16x8ShrU, + I16x8Add, I16x8AddSatS, I16x8AddSatU, I16x8Sub, I16x8SubSatS, I16x8SubSatU, I16x8Mul, I16x8MinS, + I16x8MinU, I16x8MaxS, I16x8MaxU, I16x8AvgrU, I16x8ExtMulLowI8x16S, + I16x8ExtMulHighI8x16S, I16x8ExtMulLowI8x16U, I16x8ExtMulHighI8x16U, I32x4ExtAddPairwiseI16x8S, + I32x4ExtAddPairwiseI16x8U, I32x4Abs, I32x4Neg, I32x4AllTrue, I32x4Bitmask, I32x4ExtendLowI16x8S, + I32x4ExtendHighI16x8S, I32x4ExtendLowI16x8U, I32x4ExtendHighI16x8U, I32x4Shl, I32x4ShrS, I32x4ShrU, + I32x4Add, I32x4Sub, I32x4Mul, I32x4MinS, I32x4MinU, I32x4MaxS, I32x4MaxU, I32x4DotI16x8S, + I32x4ExtMulLowI16x8S, I32x4ExtMulHighI16x8S, I32x4ExtMulLowI16x8U, I32x4ExtMulHighI16x8U, I64x2Abs, + I64x2Neg, I64x2AllTrue, I64x2Bitmask, I64x2ExtendLowI32x4S, I64x2ExtendHighI32x4S, + I64x2ExtendLowI32x4U, I64x2ExtendHighI32x4U, I64x2Shl, I64x2ShrS, I64x2ShrU, I64x2Add, I64x2Sub, + I64x2Mul, I64x2ExtMulLowI32x4S, I64x2ExtMulHighI32x4S, I64x2ExtMulLowI32x4U, I64x2ExtMulHighI32x4U, + F32x4Ceil, F32x4Floor, F32x4Trunc, F32x4Nearest, F32x4Abs, F32x4Neg, F32x4Sqrt, F32x4Add, F32x4Sub, + F32x4Mul, F32x4Div, F32x4Min, F32x4Max, F32x4PMin, F32x4PMax, F64x2Ceil, F64x2Floor, F64x2Trunc, + F64x2Nearest, F64x2Abs, F64x2Neg, F64x2Sqrt, F64x2Add, F64x2Sub, F64x2Mul, F64x2Div, F64x2Min, + F64x2Max, F64x2PMin, F64x2PMax, I32x4TruncSatF32x4S, I32x4TruncSatF32x4U, F32x4ConvertI32x4S, + F32x4ConvertI32x4U, I32x4TruncSatF64x2SZero, I32x4TruncSatF64x2UZero, F64x2ConvertLowI32x4S, + F64x2ConvertLowI32x4U, F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4, I8x16RelaxedSwizzle, + I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, I32x4RelaxedTruncF64x2SZero, + I32x4RelaxedTruncF64x2UZero, F32x4RelaxedMadd, F32x4RelaxedNmadd, F64x2RelaxedMadd, + F64x2RelaxedNmadd, I8x16RelaxedLaneselect, I16x8RelaxedLaneselect, I32x4RelaxedLaneselect, + I64x2RelaxedLaneselect, F32x4RelaxedMin, F32x4RelaxedMax, F64x2RelaxedMin, F64x2RelaxedMax, + I16x8RelaxedQ15mulrS, I16x8RelaxedDotI8x16I7x16S, I32x4RelaxedDotI8x16I7x16AddS + ) => u64::MAX, + }; + ink +} diff --git a/arbitrator/prover/src/programs/mod.rs b/arbitrator/prover/src/programs/mod.rs new file mode 100644 index 000000000..a5df2e31a --- /dev/null +++ b/arbitrator/prover/src/programs/mod.rs @@ -0,0 +1,479 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ + binary::{ExportKind, WasmBinary}, + machine::Module, + memory::MemoryType, + programs::config::CompileConfig, + value::{FunctionType as ArbFunctionType, Value}, +}; +use arbutil::{math::SaturatingSum, Bytes32, Color}; +use eyre::{bail, eyre, Report, Result, WrapErr}; +use fnv::FnvHashMap as HashMap; +use std::fmt::Debug; +use wasmer_types::{ + entity::EntityRef, FunctionIndex, GlobalIndex, GlobalInit, ImportIndex, LocalFunctionIndex, + SignatureIndex, Type, +}; +use wasmparser::{Operator, ValType}; + +#[cfg(feature = "native")] +use { + super::value, + std::marker::PhantomData, + wasmer::{ + ExportIndex, FunctionMiddleware, GlobalType, MiddlewareError, ModuleMiddleware, Mutability, + }, + wasmer_types::{MemoryIndex, ModuleInfo}, +}; + +pub mod config; +pub mod counter; +pub mod depth; +pub mod dynamic; +pub mod heap; +pub mod memory; +pub mod meter; +pub mod prelude; +pub mod start; + +pub const STYLUS_ENTRY_POINT: &str = "user_entrypoint"; + +pub trait ModuleMod { + fn add_global(&mut self, name: &str, ty: Type, init: GlobalInit) -> Result; + fn get_global(&mut self, name: &str) -> Result; + fn get_signature(&self, sig: SignatureIndex) -> Result; + fn get_function(&self, func: FunctionIndex) -> Result; + fn all_functions(&self) -> Result>; + fn all_signatures(&self) -> Result>; + fn get_import(&self, module: &str, name: &str) -> Result; + /// Moves the start function, returning true if present. + fn move_start_function(&mut self, name: &str) -> Result; + /// Drops debug-only info like export names. + fn drop_exports_and_names(&mut self, keep: &HashMap<&str, ExportKind>); + fn memory_info(&self) -> Result; +} + +pub trait Middleware { + type FM<'a>: FuncMiddleware<'a> + Debug; + + fn update_module(&self, module: &mut M) -> Result<()>; // not mutable due to wasmer + fn instrument<'a>(&self, func_index: LocalFunctionIndex) -> Result>; + fn name(&self) -> &'static str; +} + +pub trait FuncMiddleware<'a> { + /// Provide info on the function's locals. This is called before feed. + fn locals_info(&mut self, _locals: &[ValType]) {} + + /// Processes the given operator. + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>; + + /// The name of the middleware + fn name(&self) -> &'static str; +} + +#[derive(Debug)] +pub struct DefaultFuncMiddleware; + +impl<'a> FuncMiddleware<'a> for DefaultFuncMiddleware { + fn feed(&mut self, op: Operator<'a>, out: &mut O) -> Result<()> + where + O: Extend>, + { + out.extend([op]); + Ok(()) + } + + fn name(&self) -> &'static str { + "default middleware" + } +} + +/// This wrapper exists to impl wasmer's `ModuleMiddleware` generically. +/// We can't use `T` directly since we don't define `ModuleMiddleware`, +/// and we need `M` to be part of the type. +#[cfg(feature = "native")] +#[derive(Debug)] +pub struct MiddlewareWrapper(pub T, PhantomData) +where + T: Middleware + Debug + Send + Sync, + M: ModuleMod; + +#[cfg(feature = "native")] +impl MiddlewareWrapper +where + T: Middleware + Debug + Send + Sync, + M: ModuleMod, +{ + pub fn new(middleware: T) -> Self { + Self(middleware, PhantomData) + } +} + +#[cfg(feature = "native")] +impl ModuleMiddleware for MiddlewareWrapper +where + T: Middleware + Debug + Send + Sync + 'static, +{ + fn transform_module_info(&self, module: &mut ModuleInfo) -> Result<(), MiddlewareError> { + let error = |err| MiddlewareError::new(self.0.name().red(), format!("{:?}", err)); + self.0.update_module(module).map_err(error) + } + + fn generate_function_middleware<'a>( + &self, + local_function_index: LocalFunctionIndex, + ) -> Box + 'a> { + let worker = self.0.instrument(local_function_index).unwrap(); + Box::new(FuncMiddlewareWrapper(worker, PhantomData)) + } +} + +/// This wrapper exists to impl wasmer's `FunctionMiddleware` generically. +/// The logic is analogous to that of `ModuleMiddleware`, except this time +/// we need a phantom marker to parameterize by `T`'s reference's lifetime. +#[cfg(feature = "native")] +#[derive(Debug)] +pub struct FuncMiddlewareWrapper<'a, T: 'a>(T, PhantomData<&'a T>) +where + T: FuncMiddleware<'a> + Debug; + +#[cfg(feature = "native")] +impl<'a, T> FunctionMiddleware<'a> for FuncMiddlewareWrapper<'a, T> +where + T: FuncMiddleware<'a> + Debug, +{ + fn locals_info(&mut self, locals: &[ValType]) { + self.0.locals_info(locals); + } + + fn feed( + &mut self, + op: Operator<'a>, + out: &mut wasmer::MiddlewareReaderState<'a>, + ) -> Result<(), MiddlewareError> { + let name = self.0.name().red(); + let error = |err| MiddlewareError::new(name, format!("{:?}", err)); + self.0.feed(op, out).map_err(error) + } +} + +#[cfg(feature = "native")] +impl ModuleMod for ModuleInfo { + fn add_global(&mut self, name: &str, ty: Type, init: GlobalInit) -> Result { + let global_type = GlobalType::new(ty, Mutability::Var); + let name = name.to_owned(); + if self.exports.contains_key(&name) { + bail!("wasm already contains {}", name.red()) + } + let index = self.globals.push(global_type); + self.exports.insert(name, ExportIndex::Global(index)); + self.global_initializers.push(init); + Ok(index) + } + + fn get_global(&mut self, name: &str) -> Result { + let Some(ExportIndex::Global(global)) = self.exports.get(name) else { + bail!("missing global {}", name.red()) + }; + Ok(*global) + } + + fn get_signature(&self, sig: SignatureIndex) -> Result { + let error = Report::msg(format!("missing signature {}", sig.as_u32().red())); + let ty = self.signatures.get(sig).cloned().ok_or(error)?; + let ty = value::parser_func_type(ty); + ty.try_into() + } + + fn get_function(&self, func: FunctionIndex) -> Result { + let index = func.as_u32(); + match self.functions.get(func) { + Some(sig) => self.get_signature(*sig), + None => match self.function_names.get(&func) { + Some(name) => bail!("missing func {} @ index {}", name.red(), index.red()), + None => bail!("missing func @ index {}", index.red()), + }, + } + } + + fn all_functions(&self) -> Result> { + let mut funcs = HashMap::default(); + for (func, sig) in &self.functions { + let ty = self.get_signature(*sig)?; + funcs.insert(func, ty); + } + Ok(funcs) + } + + fn all_signatures(&self) -> Result> { + let mut signatures = HashMap::default(); + for (index, _) in &self.signatures { + let ty = self.get_signature(index)?; + signatures.insert(index, ty); + } + Ok(signatures) + } + + fn get_import(&self, module: &str, name: &str) -> Result { + self.imports + .iter() + .find(|(k, _)| k.module == module && k.field == name) + .map(|(_, v)| v.clone()) + .ok_or_else(|| eyre!("missing import {}", name.red())) + } + + fn move_start_function(&mut self, name: &str) -> Result { + if let Some(prior) = self.exports.get(name) { + bail!("function {} already exists @ index {:?}", name.red(), prior) + } + + let start = self.start_function.take(); + if let Some(start) = start { + let export = ExportIndex::Function(start); + self.exports.insert(name.to_owned(), export); + self.function_names.insert(start, name.to_owned()); + } + Ok(start.is_some()) + } + + fn drop_exports_and_names(&mut self, keep: &HashMap<&str, ExportKind>) { + self.exports.retain(|name, export| { + keep.get(name.as_str()) + .map_or(false, |x| *x == (*export).into()) + }); + self.function_names.clear(); + } + + fn memory_info(&self) -> Result { + if self.memories.is_empty() { + bail!("missing memory export with name {}", "memory".red()); + } + if self.memories.len() > 1 { + bail!("only one memory is allowed"); + } + if self.exports.get("memory") != Some(&ExportIndex::Memory(MemoryIndex::from_u32(0))) { + bail!("missing memory with export name {}", "memory".red()); + } + Ok(self.memories.last().unwrap().into()) + } +} + +impl<'a> ModuleMod for WasmBinary<'a> { + fn add_global(&mut self, name: &str, _ty: Type, init: GlobalInit) -> Result { + let global = match init { + GlobalInit::I32Const(x) => Value::I32(x as u32), + GlobalInit::I64Const(x) => Value::I64(x as u64), + GlobalInit::F32Const(x) => Value::F32(x), + GlobalInit::F64Const(x) => Value::F64(x), + ty => bail!("cannot add global of type {:?}", ty), + }; + if self.exports.contains_key(name) { + bail!("wasm already contains {}", name.red()) + } + let name = name.to_owned(); + let index = self.globals.len() as u32; + self.exports.insert(name, (index, ExportKind::Global)); + self.globals.push(global); + Ok(GlobalIndex::from_u32(index)) + } + + fn get_global(&mut self, name: &str) -> Result { + let Some((global, ExportKind::Global)) = self.exports.get(name) else { + bail!("missing global {}", name.red()) + }; + Ok(GlobalIndex::from_u32(*global)) + } + + fn get_signature(&self, sig: SignatureIndex) -> Result { + let index = sig.as_u32() as usize; + let error = Report::msg(format!("missing signature {}", index.red())); + self.types.get(index).cloned().ok_or(error) + } + + fn get_function(&self, func: FunctionIndex) -> Result { + let mut index = func.as_u32() as usize; + + let sig = if index < self.imports.len() { + self.imports.get(index).map(|x| &x.offset) + } else { + index -= self.imports.len(); + self.functions.get(index) + }; + + let func = func.as_u32(); + match sig { + Some(sig) => self.get_signature(SignatureIndex::from_u32(*sig)), + None => match self.names.functions.get(&func) { + Some(name) => bail!("missing func {} @ index {}", name.red(), func.red()), + None => bail!("missing func @ index {}", func.red()), + }, + } + } + + fn all_functions(&self) -> Result> { + let mut funcs = HashMap::default(); + let mut index = 0; + for import in &self.imports { + let ty = self.get_signature(SignatureIndex::from_u32(import.offset))?; + funcs.insert(FunctionIndex::new(index), ty); + index += 1; + } + for sig in &self.functions { + let ty = self.get_signature(SignatureIndex::from_u32(*sig))?; + funcs.insert(FunctionIndex::new(index), ty); + index += 1; + } + Ok(funcs) + } + + fn all_signatures(&self) -> Result> { + let mut signatures = HashMap::default(); + for (index, ty) in self.types.iter().enumerate() { + let sig = SignatureIndex::new(index); + signatures.insert(sig, ty.clone()); + } + Ok(signatures) + } + + fn get_import(&self, module: &str, name: &str) -> Result { + self.imports + .iter() + .position(|x| x.module == module && x.name == name) + .map(|x| ImportIndex::Function(FunctionIndex::from_u32(x as u32))) + .ok_or_else(|| eyre!("missing import {}", name.red())) + } + + fn move_start_function(&mut self, name: &str) -> Result { + if let Some(prior) = self.exports.get(name) { + bail!("function {} already exists @ index {:?}", name.red(), prior) + } + + let start = self.start.take(); + if let Some(start) = start { + let name = name.to_owned(); + self.exports.insert(name.clone(), (start, ExportKind::Func)); + self.names.functions.insert(start, name); + } + Ok(start.is_some()) + } + + fn drop_exports_and_names(&mut self, keep: &HashMap<&str, ExportKind>) { + self.exports + .retain(|name, ty| keep.get(name.as_str()).map_or(false, |x| *x == ty.1)); + self.names.functions.clear(); + } + + fn memory_info(&self) -> Result { + if self.memories.is_empty() { + bail!("missing memory export with name {}", "memory".red()); + } + if self.memories.len() > 1 { + bail!("only one memory is allowed"); + } + if self.exports.get("memory") != Some(&(0, ExportKind::Memory)) { + bail!("missing memory with export name {}", "memory".red()); + } + self.memories.last().unwrap().try_into() + } +} + +/// Information about an activated program. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct StylusData { + /// Global index for the amount of ink left. + pub ink_left: u32, + /// Global index for whether the program is out of ink. + pub ink_status: u32, + /// Global index for the amount of stack space remaining. + pub depth_left: u32, + /// Cost paid to invoke the program. See `programs.go` for the translation to gas. + pub init_cost: u16, + /// Cost paid to invoke the program when stored in the init cache. + pub cached_init_cost: u16, + /// Canonical estimate of the asm length in bytes. + pub asm_estimate: u32, + /// Initial memory size in pages. + pub footprint: u16, + /// Entrypoint offset. + pub user_main: u32, +} + +impl StylusData { + pub fn global_offsets(&self) -> (u64, u64, u64) { + ( + self.ink_left as u64, + self.ink_status as u64, + self.depth_left as u64, + ) + } +} + +impl Module { + pub fn activate( + wasm: &[u8], + codehash: &Bytes32, + version: u16, + page_limit: u16, + debug: bool, + gas: &mut u64, + ) -> Result<(Self, StylusData)> { + // converts a number of microseconds to gas + // TODO: collapse to a single value after finalizing factors + let us_to_gas = |us: u64| { + let fudge = 2; + let sync_rate = 1_000_000 / 2; + let speed = 7_000_000; + us.saturating_mul(fudge * speed) / sync_rate + }; + + macro_rules! pay { + ($us:expr) => { + let amount = us_to_gas($us); + if *gas < amount { + *gas = 0; + bail!("out of gas"); + } + *gas -= amount; + }; + } + + // pay for wasm + let wasm_len = wasm.len() as u64; + pay!(wasm_len.saturating_mul(31_733 / 100_000)); + + let compile = CompileConfig::version(version, debug); + let (bin, stylus_data) = WasmBinary::parse_user(wasm, page_limit, &compile, codehash) + .wrap_err("failed to parse wasm")?; + + // pay for funcs + let funcs = bin.functions.len() as u64; + pay!(funcs.saturating_mul(17_263) / 100_000); + + // pay for data + let data = bin.datas.iter().map(|x| x.data.len()).saturating_sum() as u64; + pay!(data.saturating_mul(17_376) / 100_000); + + // pay for elements + let elems = bin.elements.iter().map(|x| x.range.len()).saturating_sum() as u64; + pay!(elems.saturating_mul(17_376) / 100_000); + + // pay for memory + let mem = bin.memories.first().map(|x| x.initial).unwrap_or_default(); + pay!(mem.saturating_mul(2217)); + + // pay for code + let code = bin.codes.iter().map(|x| x.expr.len()).saturating_sum() as u64; + pay!(code.saturating_mul(535) / 1_000); + + let module = Self::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) + .wrap_err("failed to build user module")?; + + Ok((module, stylus_data)) + } +} diff --git a/arbitrator/prover/src/programs/prelude.rs b/arbitrator/prover/src/programs/prelude.rs new file mode 100644 index 000000000..4db8e0341 --- /dev/null +++ b/arbitrator/prover/src/programs/prelude.rs @@ -0,0 +1,12 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +pub use super::{ + config::{CompileConfig, StylusConfig}, + counter::CountingMachine, + depth::DepthCheckedMachine, + meter::{GasMeteredMachine, MachineMeter, MeteredMachine}, +}; + +#[cfg(feature = "native")] +pub use super::start::StartlessMachine; diff --git a/arbitrator/prover/src/programs/start.rs b/arbitrator/prover/src/programs/start.rs new file mode 100644 index 000000000..d3a19942f --- /dev/null +++ b/arbitrator/prover/src/programs/start.rs @@ -0,0 +1,67 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::{ + binary::ExportKind, + programs::{DefaultFuncMiddleware, Middleware, ModuleMod, STYLUS_ENTRY_POINT}, +}; +use eyre::{bail, Result}; +use fnv::FnvHashMap as HashMap; +use lazy_static::lazy_static; +use wasmer_types::LocalFunctionIndex; + +#[cfg(feature = "native")] +use wasmer::TypedFunction; + +lazy_static! { + /// Lists the exports a user program map have + static ref EXPORT_WHITELIST: HashMap<&'static str, ExportKind> = { + let mut map = HashMap::default(); + map.insert(STYLUS_ENTRY_POINT, ExportKind::Func); + map.insert(StartMover::NAME, ExportKind::Func); + map.insert("memory", ExportKind::Memory); + map + }; +} + +#[derive(Debug)] +pub struct StartMover { + /// Whether to keep offchain information. + debug: bool, +} + +impl StartMover { + pub const NAME: &'static str = "stylus_start"; + + pub fn new(debug: bool) -> Self { + Self { debug } + } +} + +impl Middleware for StartMover { + type FM<'a> = DefaultFuncMiddleware; + + fn update_module(&self, module: &mut M) -> Result<()> { + let had_start = module.move_start_function(Self::NAME)?; + if had_start && !self.debug { + bail!("start functions not allowed"); + } + if !self.debug { + module.drop_exports_and_names(&EXPORT_WHITELIST); + } + Ok(()) + } + + fn instrument<'a>(&self, _: LocalFunctionIndex) -> Result> { + Ok(DefaultFuncMiddleware) + } + + fn name(&self) -> &'static str { + "start mover" + } +} + +#[cfg(feature = "native")] +pub trait StartlessMachine { + fn get_start(&self) -> Result>; +} diff --git a/arbitrator/prover/src/test.rs b/arbitrator/prover/src/test.rs new file mode 100644 index 000000000..97170441f --- /dev/null +++ b/arbitrator/prover/src/test.rs @@ -0,0 +1,71 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![cfg(test)] + +use crate::binary; +use brotli::Dictionary; +use eyre::Result; +use std::path::Path; + +fn as_wasm(wat: &str) -> Vec { + let wasm = wasmer::wat2wasm(wat.as_bytes()); + wasm.unwrap().to_vec() +} + +#[test] +pub fn reject_reexports() { + let wasm = as_wasm( + r#" + (module + (import "env" "some_hostio_func" (func (param) (result))) + (func $should_reject (export "some_hostio_func") (param) (result)) + )"#, + ); + let _ = binary::parse(&wasm, Path::new("")).unwrap_err(); + + let wasm = as_wasm( + r#" + (module + (import "env" "some_hostio_func" (func (param) (result))) + (global $should_reject (export "some_hostio_func") f32 (f32.const 0)) + )"#, + ); + let _ = binary::parse(&wasm, Path::new("")).unwrap_err(); +} + +#[test] +pub fn reject_ambiguous_imports() { + let wasm = as_wasm( + r#" + (module + (import "vm_hooks" "some_import" (func (param i64) (result i64 i32))) + (import "vm_hooks" "some_import" (func (param i64) (result i64 i32))) + )"#, + ); + let _ = binary::parse(&wasm, Path::new("")).unwrap(); + + let wasm = as_wasm( + r#" + (module + (import "vm_hooks" "some_import" (func (param i32) (result f64))) + (import "vm_hooks" "some_import" (func (param i32) (result))) + )"#, + ); + let _ = binary::parse(&wasm, Path::new("")).unwrap_err(); +} + +#[test] +pub fn test_compress() -> Result<()> { + let data = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); + let mut last = vec![]; + + for dict in [Dictionary::Empty, Dictionary::StylusProgram] { + let deflate = brotli::compress(data, 11, 22, dict).unwrap(); + let inflate = brotli::decompress(&deflate, dict).unwrap(); + assert_eq!(hex::encode(inflate), hex::encode(data)); + assert!(&deflate != &last); + last = deflate; + } + Ok(()) +} diff --git a/arbitrator/prover/src/utils.rs b/arbitrator/prover/src/utils.rs index 584a2fc7d..841de0d25 100644 --- a/arbitrator/prover/src/utils.rs +++ b/arbitrator/prover/src/utils.rs @@ -1,9 +1,11 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +#[cfg(feature = "native")] use crate::kzg::ETHEREUM_KZG_SETTINGS; use arbutil::PreimageType; use ark_serialize::CanonicalSerialize; +#[cfg(feature = "native")] use c_kzg::{Blob, KzgCommitment}; use digest::Digest; use eyre::{eyre, Result}; @@ -11,106 +13,8 @@ use kzgbn254::{blob::Blob as EigenDABlob, kzg::Kzg as KzgBN254, polynomial::Poly use serde::{Deserialize, Serialize}; use sha2::Sha256; use sha3::Keccak256; -use std::{ - borrow::Borrow, - convert::TryInto, - fmt, - fs::File, - io::Read, - ops::{Deref, DerefMut}, - path::Path, -}; -use wasmparser::{TableType, Type}; - -/// cbindgen:field-names=[bytes] -#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[repr(C)] -pub struct Bytes32(pub [u8; 32]); - -impl Deref for Bytes32 { - type Target = [u8; 32]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Bytes32 { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl AsRef<[u8]> for Bytes32 { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl Borrow<[u8]> for Bytes32 { - fn borrow(&self) -> &[u8] { - &self.0 - } -} - -impl From<[u8; 32]> for Bytes32 { - fn from(x: [u8; 32]) -> Self { - Self(x) - } -} - -impl From for Bytes32 { - fn from(x: u32) -> Self { - let mut b = [0u8; 32]; - b[(32 - 4)..].copy_from_slice(&x.to_be_bytes()); - Self(b) - } -} - -impl From for Bytes32 { - fn from(x: u64) -> Self { - let mut b = [0u8; 32]; - b[(32 - 8)..].copy_from_slice(&x.to_be_bytes()); - Self(b) - } -} - -impl From for Bytes32 { - fn from(x: usize) -> Self { - let mut b = [0u8; 32]; - b[(32 - (usize::BITS as usize / 8))..].copy_from_slice(&x.to_be_bytes()); - Self(b) - } -} - -impl IntoIterator for Bytes32 { - type Item = u8; - type IntoIter = std::array::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - IntoIterator::into_iter(self.0) - } -} - -type GenericBytes32 = digest::generic_array::GenericArray; - -impl From for Bytes32 { - fn from(x: GenericBytes32) -> Self { - <[u8; 32]>::from(x).into() - } -} - -impl fmt::Display for Bytes32 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(self)) - } -} - -impl fmt::Debug for Bytes32 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(self)) - } -} +use std::{borrow::Borrow, convert::TryInto, fmt, fs::File, io::Read, ops::Deref, path::Path}; +use wasmparser::{RefType, TableType}; /// A Vec allocated with libc::malloc pub struct CBytes { @@ -173,23 +77,41 @@ impl From<&[u8]> for CBytes { unsafe impl Send for CBytes {} unsafe impl Sync for CBytes {} +/// Unfortunately, [`wasmparser::RefType`] isn't serde and its contents aren't public. +/// This type enables serde via a 1:1 transmute. #[derive(Serialize, Deserialize)] -#[serde(remote = "Type")] -enum RemoteType { - I32, - I64, - F32, - F64, - V128, - FuncRef, - ExternRef, +struct RemoteRefType(pub [u8; 3]); + +impl From for RemoteRefType { + fn from(value: RefType) -> Self { + unsafe { std::mem::transmute(value) } + } +} + +impl From for RefType { + fn from(value: RemoteRefType) -> Self { + unsafe { std::mem::transmute(value) } + } +} + +mod remote_convert { + use super::{RefType, RemoteRefType}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(value: &RefType, serializer: S) -> Result { + RemoteRefType::from(*value).serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + Ok(RemoteRefType::deserialize(deserializer)?.into()) + } } #[derive(Serialize, Deserialize)] #[serde(remote = "TableType")] pub struct RemoteTableType { - #[serde(with = "RemoteType")] - pub element_type: Type, + #[serde(with = "remote_convert")] + pub element_type: RefType, pub initial: u32, pub maximum: Option, } @@ -270,6 +192,7 @@ pub fn split_import(qualified: &str) -> Result<(&str, &str)> { Ok((module, name)) } +#[cfg(feature = "native")] pub fn hash_preimage(preimage: &[u8], ty: PreimageType) -> Result<[u8; 32]> { match ty { PreimageType::Keccak256 => Ok(Keccak256::digest(preimage).into()), diff --git a/arbitrator/prover/src/value.rs b/arbitrator/prover/src/value.rs index c63486bd0..4ec02f546 100644 --- a/arbitrator/prover/src/value.rs +++ b/arbitrator/prover/src/value.rs @@ -1,15 +1,19 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use crate::{binary::FloatType, utils::Bytes32}; -use arbutil::Color; +use crate::binary::FloatType; +use arbutil::{Bytes32, Color}; use digest::Digest; -use eyre::{bail, Result}; +use eyre::{bail, ErrReport, Result}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, TryFromInto}; use sha3::Keccak256; -use std::{convert::TryFrom, fmt::Display}; -use wasmparser::{FuncType, Type}; +use std::{ + convert::{TryFrom, TryInto}, + fmt::Display, + ops::Add, +}; +use wasmparser::{FuncType, RefType, ValType}; #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] #[repr(u8)] @@ -29,23 +33,71 @@ impl ArbValueType { } } -impl TryFrom for ArbValueType { +impl TryFrom for ArbValueType { type Error = eyre::Error; - fn try_from(ty: Type) -> Result { - use Type::*; + fn try_from(ty: ValType) -> Result { + use ValType as V; Ok(match ty { - I32 => Self::I32, - I64 => Self::I64, - F32 => Self::F32, - F64 => Self::F64, - FuncRef => Self::FuncRef, - ExternRef => Self::FuncRef, - V128 => bail!("128-bit types are not supported"), + V::I32 => Self::I32, + V::I64 => Self::I64, + V::F32 => Self::F32, + V::F64 => Self::F64, + V::Ref(ty) => ty.try_into()?, + V::V128 => bail!("128-bit types are not supported"), }) } } +impl TryFrom for ArbValueType { + type Error = eyre::Error; + + fn try_from(value: RefType) -> Result { + Ok(match value { + RefType::FUNCREF => Self::FuncRef, + RefType::EXTERNREF => Self::FuncRef, + RefType::NULLREF => Self::RefNull, + _ => bail!("ref extensions not supported"), + }) + } +} + +impl From for ValType { + fn from(ty: ArbValueType) -> Self { + use ArbValueType as V; + match ty { + V::I32 => Self::I32, + V::I64 => Self::I64, + V::F32 => Self::F32, + V::F64 => Self::F64, + V::RefNull => Self::Ref(RefType::NULLREF), + V::FuncRef => Self::Ref(RefType::FUNCREF), + V::InternalRef => Self::Ref(RefType::FUNCREF), // not analogous, but essentially a func pointer + } + } +} + +#[cfg(feature = "native")] +pub fn parser_type(ty: &wasmer::Type) -> wasmer::wasmparser::ValType { + match ty { + wasmer::Type::I32 => wasmer::wasmparser::ValType::I32, + wasmer::Type::I64 => wasmer::wasmparser::ValType::I64, + wasmer::Type::F32 => wasmer::wasmparser::ValType::F32, + wasmer::Type::F64 => wasmer::wasmparser::ValType::F64, + wasmer::Type::V128 => wasmer::wasmparser::ValType::V128, + wasmer::Type::ExternRef => wasmer::wasmparser::ValType::Ref(RefType::EXTERNREF), + wasmer::Type::FuncRef => wasmer::wasmparser::ValType::Ref(RefType::FUNCREF), + } +} + +#[cfg(feature = "native")] +pub fn parser_func_type(ty: wasmer::FunctionType) -> FuncType { + let convert = |t: &[wasmer::Type]| -> Vec { t.iter().map(parser_type).collect() }; + let params = convert(ty.params()); + let results = convert(ty.results()); + FuncType::new(params, results) +} + impl From for ArbValueType { fn from(ty: FloatType) -> ArbValueType { match ty { @@ -112,6 +164,16 @@ impl ProgramCounter { } } +impl Add for ProgramCounter { + type Output = ProgramCounter; + + fn add(self, rhs: u32) -> Self::Output { + let mut counter = self; + counter.inst += rhs; + counter + } +} + impl Display for ProgramCounter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -282,6 +344,70 @@ impl PartialEq for Value { } } +impl From for Value { + fn from(value: u8) -> Self { + Value::I32(value.into()) + } +} + +impl From for Value { + fn from(value: u16) -> Self { + Value::I32(value.into()) + } +} + +impl From for Value { + fn from(value: u32) -> Self { + Value::I32(value) + } +} + +impl From for Value { + fn from(value: u64) -> Self { + Value::I64(value) + } +} + +impl From for Value { + fn from(value: f32) -> Self { + Value::F32(value) + } +} + +impl From for Value { + fn from(value: f64) -> Self { + Value::F64(value) + } +} + +impl From for Value { + fn from(value: ProgramCounter) -> Self { + Value::InternalRef(value) + } +} + +impl TryInto for Value { + type Error = ErrReport; + + fn try_into(self) -> Result { + match self { + Value::I32(value) => Ok(value), + _ => bail!("value not a u32"), + } + } +} + +impl TryInto for Value { + type Error = ErrReport; + + fn try_into(self) -> Result { + match self { + Value::I64(value) => Ok(value), + _ => bail!("value not a u64"), + } + } +} + impl Eq for Value {} #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -291,8 +417,15 @@ pub struct FunctionType { } impl FunctionType { - pub fn new(inputs: Vec, outputs: Vec) -> FunctionType { - FunctionType { inputs, outputs } + pub fn new(inputs: T, outputs: U) -> FunctionType + where + T: Into>, + U: Into>, + { + FunctionType { + inputs: inputs.into(), + outputs: outputs.into(), + } } pub fn hash(&self) -> Bytes32 { @@ -317,13 +450,58 @@ impl TryFrom for FunctionType { let mut inputs = vec![]; let mut outputs = vec![]; - for input in func.params.iter() { + for input in func.params() { inputs.push(ArbValueType::try_from(*input)?) } - for output in func.returns.iter() { + for output in func.results() { outputs.push(ArbValueType::try_from(*output)?) } - Ok(Self { inputs, outputs }) } } + +impl Display for FunctionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut signature = "λ(".to_string(); + if !self.inputs.is_empty() { + for arg in &self.inputs { + signature += &format!("{}, ", arg); + } + signature.pop(); + signature.pop(); + } + signature += ")"; + + let output_tuple = self.outputs.len() > 2; + if !self.outputs.is_empty() { + signature += " -> "; + if output_tuple { + signature += "("; + } + for out in &self.outputs { + signature += &format!("{}, ", out); + } + signature.pop(); + signature.pop(); + if output_tuple { + signature += ")"; + } + } + write!(f, "{}", signature) + } +} + +impl Display for ArbValueType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use ArbValueType::*; + match self { + I32 => write!(f, "i32"), + I64 => write!(f, "i64"), + F32 => write!(f, "f32"), + F64 => write!(f, "f64"), + RefNull => write!(f, "null"), + FuncRef => write!(f, "func"), + InternalRef => write!(f, "internal"), + } + } +} diff --git a/arbitrator/prover/src/wavm.rs b/arbitrator/prover/src/wavm.rs index 690789b44..de016452f 100644 --- a/arbitrator/prover/src/wavm.rs +++ b/arbitrator/prover/src/wavm.rs @@ -4,9 +4,9 @@ use crate::{ binary::FloatInstruction, host::InternalFunc, - utils::Bytes32, value::{ArbValueType, FunctionType, IntegerValType}, }; +use arbutil::{Bytes32, Color, DebugColor}; use digest::Digest; use eyre::{bail, ensure, Result}; use fnv::FnvHashMap as HashMap; @@ -141,6 +141,10 @@ pub enum Opcode { Dup, /// Call a function in a different module CrossModuleCall, + /// Call a function in a different module, acting as the caller's module + CrossModuleForward, + /// Call a function in a different module provided via the stack + CrossModuleInternalCall, /// Call a caller module's internal method with a given function offset CallerModuleInternalCall, /// Gets bytes32 from global state @@ -155,8 +159,18 @@ pub enum Opcode { ReadPreImage, /// Reads the current inbox message into the pointer on the stack at an offset ReadInboxMessage, + /// Dynamically adds a module to the replay machine + LinkModule, + /// Dynamically removes the last module to the replay machine + UnlinkModule, /// Stop exexcuting the machine and move to the finished status HaltAndSetFinished, + /// create cothread (cannot be called from cothread) + NewCoThread, + /// pop cothread (cannot be called from cothread) + PopCoThread, + /// switch between main and a cothread + SwitchThread, } impl Opcode { @@ -259,6 +273,8 @@ impl Opcode { Opcode::MoveFromInternalToStack => 0x8006, Opcode::Dup => 0x8008, Opcode::CrossModuleCall => 0x8009, + Opcode::CrossModuleForward => 0x800B, + Opcode::CrossModuleInternalCall => 0x800C, Opcode::CallerModuleInternalCall => 0x800A, Opcode::GetGlobalStateBytes32 => 0x8010, Opcode::SetGlobalStateBytes32 => 0x8011, @@ -266,7 +282,12 @@ impl Opcode { Opcode::SetGlobalStateU64 => 0x8013, Opcode::ReadPreImage => 0x8020, Opcode::ReadInboxMessage => 0x8021, + Opcode::LinkModule => 0x8023, + Opcode::UnlinkModule => 0x8024, Opcode::HaltAndSetFinished => 0x8022, + Opcode::NewCoThread => 0x8030, + Opcode::PopCoThread => 0x8031, + Opcode::SwitchThread => 0x8032, } } @@ -337,18 +358,24 @@ impl Instruction { } } - pub fn serialize_for_proof(self) -> [u8; 34] { - let mut ret = [0u8; 34]; - ret[..2].copy_from_slice(&self.opcode.repr().to_be_bytes()); - ret[2..].copy_from_slice(&*self.get_proving_argument_data()); - ret + pub fn serialize_for_proof(code: &[Self]) -> Vec { + let mut data = Vec::with_capacity(1 + 34 * code.len()); + data.push(code.len() as u8); + for inst in code { + data.extend(inst.opcode.repr().to_be_bytes()); + data.extend(inst.get_proving_argument_data()); + } + data } - pub fn hash(self) -> Bytes32 { + pub fn hash(code: &[Self]) -> Bytes32 { let mut h = Keccak256::new(); - h.update(b"Instruction:"); - h.update(self.opcode.repr().to_be_bytes()); - h.update(self.get_proving_argument_data()); + h.update(b"Instructions:"); + h.update((code.len() as u8).to_be_bytes()); + for inst in code { + h.update(inst.opcode.repr().to_be_bytes()); + h.update(inst.get_proving_argument_data()); + } h.finalize().into() } } @@ -416,14 +443,8 @@ impl Sub for StackState { type Output = isize; fn sub(self, rhs: Self) -> Self::Output { - let s = match self { - Self::Reachable(s) => s, - Self::Unreachable => return 0, - }; - let rhs = match rhs { - Self::Reachable(rhs) => rhs, - Self::Unreachable => return 0, - }; + let Self::Reachable(s) = self else { return 0 }; + let Self::Reachable(rhs) = rhs else { return 0 }; s as isize - rhs as isize } } @@ -436,6 +457,7 @@ pub fn wasm_to_wavm( all_types: &[FunctionType], all_types_func_idx: u32, internals_offset: u32, + name: &str, ) -> Result<()> { use Operator::*; @@ -564,9 +586,8 @@ pub fn wasm_to_wavm( let func = $func; let sig = func.signature(); - let (module, func) = match fp_impls.get(&func) { - Some((module, func)) => (module, func), - None => bail!("No implementation for floating point operation {:?}", &func), + let Some((module, func)) = fp_impls.get(&func) else { + bail!("No implementation for floating point operation {} in {}", func.debug_red(), name.red()); }; // Reinterpret float args into ints @@ -702,17 +723,17 @@ pub fn wasm_to_wavm( opcode!(Unreachable); stack = StackState::Unreachable; }, - Nop => opcode!(Nop), - Block { ty } => { - scopes.push(Scope::Simple(*ty, vec![], height_after_block!(ty))); + Nop => {}, + Block { blockty } => { + scopes.push(Scope::Simple(*blockty, vec![], height_after_block!(blockty))); } - Loop { ty } => { - scopes.push(Scope::Loop(*ty, out.len(), stack, height_after_block!(ty))); + Loop { blockty } => { + scopes.push(Scope::Loop(*blockty, out.len(), stack, height_after_block!(blockty))); } - If { ty } => { + If { blockty } => { opcode!(I32Eqz); stack -= 1; // the else block shouldn't have the conditional that gets popped next instruction - scopes.push(Scope::IfElse(*ty, vec![], Some(out.len()), stack, height_after_block!(ty))); + scopes.push(Scope::IfElse(*blockty, vec![], Some(out.len()), stack, height_after_block!(blockty))); opcode!(ArbitraryJumpIf); } Else => { @@ -725,12 +746,12 @@ pub fn wasm_to_wavm( *cond = None; stack = *if_height; } - x => bail!("malformed if-else scope {:?}", x), + x => bail!("malformed if-else scope {x:?}"), } } - unsupported @ dot!(Try, Catch, Throw, Rethrow) => { - bail!("exception-handling extension not supported {:?}", unsupported) + unsupported @ dot!(Try, Catch, Throw, Rethrow, ThrowRef, TryTable) => { + bail!("exception-handling extension not supported {unsupported:?}") }, End => { @@ -750,11 +771,11 @@ pub fn wasm_to_wavm( } Br { relative_depth } => branch!(ArbitraryJump, *relative_depth), BrIf { relative_depth } => branch!(ArbitraryJumpIf, *relative_depth), - BrTable { table } => { + BrTable { targets } => { let start_stack = stack; // evaluate each branch let mut subjumps = vec![]; - for (index, target) in table.targets().enumerate() { + for (index, target) in targets.targets().enumerate() { opcode!(Dup, @push 1); opcode!(I32Const, index as u64, @push 1); compare!(I32, Eq, false); @@ -764,7 +785,7 @@ pub fn wasm_to_wavm( // nothing matched: drop the index and jump to the default. opcode!(Drop, @pop 1); - branch!(ArbitraryJump, table.default()); + branch!(ArbitraryJump, targets.default()); // simulate a jump table of branches for (jump, branch) in subjumps { @@ -777,25 +798,29 @@ pub fn wasm_to_wavm( Return => branch!(ArbitraryJump, scopes.len() - 1), Call { function_index } => call!(*function_index), - CallIndirect { index, table_index, .. } => { - let ty = &all_types[*index as usize]; + CallIndirect { type_index, table_index, .. } => { + let ty = &all_types[*type_index as usize]; let delta = ty.outputs.len() as isize - ty.inputs.len() as isize; - opcode!(CallIndirect, pack_call_indirect(*table_index, *index), @push delta - 1); + opcode!(CallIndirect, pack_call_indirect(*table_index, *type_index), @push delta - 1); } unsupported @ dot!(ReturnCall, ReturnCallIndirect) => { - bail!("tail-call extension not supported {:?}", unsupported) + bail!("tail-call extension not supported {unsupported:?}") + } + + unsupported @ dot!(CallRef, ReturnCallRef) => { + bail!("typed function references extension not supported {unsupported:?}") } unsupported @ (dot!(Delegate) | op!(CatchAll)) => { - bail!("exception-handling extension not supported {:?}", unsupported) + bail!("exception-handling extension not supported {unsupported:?}") }, Drop => opcode!(Drop, @pop 1), Select => opcode!(Select, @pop 2), unsupported @ dot!(TypedSelect) => { - bail!("reference-types extension not supported {:?}", unsupported) + bail!("reference-types extension not supported {unsupported:?}") }, LocalGet { local_index } => opcode!(LocalGet, *local_index as u64, @push 1), @@ -842,10 +867,14 @@ pub fn wasm_to_wavm( F32Const { value } => opcode!(F32Const, value.bits() as u64, @push 1), F64Const { value } => opcode!(F64Const, value.bits(), @push 1), - unsupported @ (dot!(RefNull) | op!(RefIsNull) | dot!(RefFunc)) => { - bail!("reference-types extension not supported {:?}", unsupported) + unsupported @ (dot!(RefNull) | op!(RefIsNull) | dot!(RefFunc, RefEq)) => { + bail!("reference-types extension not supported {unsupported:?}") }, + unsupported @ dot!(RefAsNonNull, BrOnNull, BrOnNonNull) => { + bail!("typed function references extension not supported {unsupported:?}") + } + I32Eqz => opcode!(I32Eqz), I32Eq => compare!(I32, Eq, false), I32Ne => compare!(I32, Ne, false), @@ -987,8 +1016,8 @@ pub fn wasm_to_wavm( ensure!(*mem == 0, "multi-memory proposal not supported"); call!(internals_offset + InternalFunc::MemoryFill as u32) }, - MemoryCopy { src, dst } => { - ensure!(*src == 0 && *dst == 0, "multi-memory proposal not supported"); + MemoryCopy { src_mem, dst_mem } => { + ensure!(*src_mem == 0 && *dst_mem == 0, "multi-memory proposal not supported"); call!(internals_offset + InternalFunc::MemoryCopy as u32) }, @@ -997,7 +1026,21 @@ pub fn wasm_to_wavm( MemoryInit, DataDrop, TableInit, ElemDrop, TableCopy, TableFill, TableGet, TableSet, TableGrow, TableSize ) - ) => bail!("bulk-memory-operations extension not fully supported {:?}", unsupported), + ) => bail!("bulk-memory-operations extension not fully supported {unsupported:?}"), + + unsupported @ dot!(MemoryDiscard) => { + bail!("memory control proposal {unsupported:?}") + }, + + unsupported @ ( + dot!( + StructNew, StructNewDefault, StructGet, StructGetS, StructGetU, StructSet, + ArrayNew, ArrayNewDefault, ArrayNewFixed, ArrayNewData, ArrayNewElem, + ArrayGet, ArrayGetS, ArrayGetU, ArraySet, ArrayLen, ArrayFill, ArrayCopy, + ArrayInitData, ArrayInitElem, RefTestNonNull, RefTestNullable, RefCastNonNull, RefCastNullable, + BrOnCast, BrOnCastFail, AnyConvertExtern, ExternConvertAny, RefI31, I31GetS, I31GetU + ) + ) => bail!("garbage collection extension not supported {unsupported:?}"), unsupported @ ( dot!( @@ -1016,7 +1059,7 @@ pub fn wasm_to_wavm( I64AtomicRmw32XchgU, I32AtomicRmwCmpxchg, I64AtomicRmwCmpxchg, I32AtomicRmw8CmpxchgU, I32AtomicRmw16CmpxchgU, I64AtomicRmw8CmpxchgU, I64AtomicRmw16CmpxchgU, I64AtomicRmw32CmpxchgU ) - ) => bail!("threads extension not supported {:?}", unsupported), + ) => bail!("threads extension not supported {unsupported:?}"), unsupported @ ( dot!( @@ -1037,12 +1080,12 @@ pub fn wasm_to_wavm( V128Not, V128And, V128AndNot, V128Or, V128Xor, V128Bitselect, V128AnyTrue, I8x16Abs, I8x16Neg, I8x16Popcnt, I8x16AllTrue, I8x16Bitmask, I8x16NarrowI16x8S, I8x16NarrowI16x8U, I8x16Shl, I8x16ShrS, I8x16ShrU, I8x16Add, I8x16AddSatS, I8x16AddSatU, I8x16Sub, I8x16SubSatS, - I8x16SubSatU, I8x16MinS, I8x16MinU, I8x16MaxS, I8x16MaxU, I8x16RoundingAverageU, + I8x16SubSatU, I8x16MinS, I8x16MinU, I8x16MaxS, I8x16MaxU, I8x16AvgrU, I16x8ExtAddPairwiseI8x16S, I16x8ExtAddPairwiseI8x16U, I16x8Abs, I16x8Neg, I16x8Q15MulrSatS, I16x8AllTrue, I16x8Bitmask, I16x8NarrowI32x4S, I16x8NarrowI32x4U, I16x8ExtendLowI8x16S, I16x8ExtendHighI8x16S, I16x8ExtendLowI8x16U, I16x8ExtendHighI8x16U, I16x8Shl, I16x8ShrS, I16x8ShrU, I16x8Add, I16x8AddSatS, I16x8AddSatU, I16x8Sub, I16x8SubSatS, I16x8SubSatU, I16x8Mul, I16x8MinS, - I16x8MinU, I16x8MaxS, I16x8MaxU, I16x8RoundingAverageU, I16x8ExtMulLowI8x16S, + I16x8MinU, I16x8MaxS, I16x8MaxU, I16x8AvgrU, I16x8ExtMulLowI8x16S, I16x8ExtMulHighI8x16S, I16x8ExtMulLowI8x16U, I16x8ExtMulHighI8x16U, I32x4ExtAddPairwiseI16x8S, I32x4ExtAddPairwiseI16x8U, I32x4Abs, I32x4Neg, I32x4AllTrue, I32x4Bitmask, I32x4ExtendLowI16x8S, I32x4ExtendHighI16x8S, I32x4ExtendLowI16x8U, I32x4ExtendHighI16x8U, I32x4Shl, I32x4ShrS, I32x4ShrU, @@ -1057,14 +1100,14 @@ pub fn wasm_to_wavm( F64x2Max, F64x2PMin, F64x2PMax, I32x4TruncSatF32x4S, I32x4TruncSatF32x4U, F32x4ConvertI32x4S, F32x4ConvertI32x4U, I32x4TruncSatF64x2SZero, I32x4TruncSatF64x2UZero, F64x2ConvertLowI32x4S, F64x2ConvertLowI32x4U, F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4, I8x16RelaxedSwizzle, - I32x4RelaxedTruncSatF32x4S, I32x4RelaxedTruncSatF32x4U, I32x4RelaxedTruncSatF64x2SZero, - I32x4RelaxedTruncSatF64x2UZero, F32x4Fma, F32x4Fms, F64x2Fma, F64x2Fms, I8x16LaneSelect, - I16x8LaneSelect, I32x4LaneSelect, I64x2LaneSelect, F32x4RelaxedMin, F32x4RelaxedMax, - F64x2RelaxedMin, F64x2RelaxedMax + I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, I32x4RelaxedTruncF64x2SZero, + I32x4RelaxedTruncF64x2UZero, F32x4RelaxedMadd, F32x4RelaxedNmadd, F64x2RelaxedMadd, + F64x2RelaxedNmadd, I8x16RelaxedLaneselect, I16x8RelaxedLaneselect, I32x4RelaxedLaneselect, + I64x2RelaxedLaneselect, F32x4RelaxedMin, F32x4RelaxedMax, F64x2RelaxedMin, F64x2RelaxedMax, + I16x8RelaxedQ15mulrS, I16x8RelaxedDotI8x16I7x16S, I32x4RelaxedDotI8x16I7x16AddS ) - ) => bail!("SIMD extension not supported {:?}", unsupported) + ) => bail!("SIMD extension not supported {unsupported:?}") }; } - Ok(()) } diff --git a/arbitrator/prover/test-cases/block.wat b/arbitrator/prover/test-cases/block.wat index 32ac7a5a1..2ea3d087d 100644 --- a/arbitrator/prover/test-cases/block.wat +++ b/arbitrator/prover/test-cases/block.wat @@ -66,4 +66,9 @@ (br_if 0) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/bulk-memory.wat b/arbitrator/prover/test-cases/bulk-memory.wat index 53fc248db..3ae072853 100644 --- a/arbitrator/prover/test-cases/bulk-memory.wat +++ b/arbitrator/prover/test-cases/bulk-memory.wat @@ -79,5 +79,10 @@ local.get 1 i32.ne (if (then (unreachable)))) + + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) + ) + (start $start) - (memory (export "mem") 1)) + (memory (export "memory") 1 1)) diff --git a/arbitrator/prover/test-cases/call-indirect.wat b/arbitrator/prover/test-cases/call-indirect.wat index 6f1b8ab9f..1f6bee6d3 100644 --- a/arbitrator/prover/test-cases/call-indirect.wat +++ b/arbitrator/prover/test-cases/call-indirect.wat @@ -26,4 +26,9 @@ (i32.mul) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/call.wat b/arbitrator/prover/test-cases/call.wat index e4bcf8d12..f56849ab7 100644 --- a/arbitrator/prover/test-cases/call.wat +++ b/arbitrator/prover/test-cases/call.wat @@ -16,4 +16,9 @@ (call 1) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/const.wat b/arbitrator/prover/test-cases/const.wat index e9e46ff97..4f3ffbd8d 100644 --- a/arbitrator/prover/test-cases/const.wat +++ b/arbitrator/prover/test-cases/const.wat @@ -8,4 +8,9 @@ (drop) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/div-overflow.wat b/arbitrator/prover/test-cases/div-overflow.wat index 993185b82..a76493e74 100644 --- a/arbitrator/prover/test-cases/div-overflow.wat +++ b/arbitrator/prover/test-cases/div-overflow.wat @@ -9,4 +9,9 @@ (drop) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/dynamic.wat b/arbitrator/prover/test-cases/dynamic.wat new file mode 100644 index 000000000..8771bde87 --- /dev/null +++ b/arbitrator/prover/test-cases/dynamic.wat @@ -0,0 +1,91 @@ + +(module + (import "hostio" "wavm_link_module" (func $link (param i32) (result i32))) + (import "hostio" "wavm_unlink_module" (func $unlink )) + (import "hostio" "program_set_ink" (func $set_ink (param i32 i64) )) + (import "hostio" "program_ink_left" (func $ink_left (param i32) (result i64))) + (import "hostio" "program_ink_status" (func $ink_status (param i32) (result i32))) + (import "hostio" "program_set_stack" (func $set_stack (param i32 i32) )) + (import "hostio" "program_stack_left" (func $stack_left (param i32) (result i32))) + (import "hostio" "program_call_main" (func $user_func (param i32 i32) (result i32))) + (import "env" "wavm_halt_and_set_finished" (func $halt )) + + ;; WAVM Module hash + (data (i32.const 0x000) + "\a1\49\cf\81\13\ff\9c\95\f2\c8\c2\a1\42\35\75\36\7d\e8\6d\d4\22\d8\71\14\bb\9e\a4\7b\af\53\5d\d7") ;; user + (func $start (local $user i32) (local $internals i32) + ;; link in user.wat + i32.const 0 + call $link + local.set $user + + ;; set gas globals + local.get $user + i64.const 65536 + call $set_ink + + ;; get gas + local.get $user + call $ink_left + i64.const 65536 + i64.ne + (if (then (unreachable))) + + ;; get gas status + (call $ink_status (local.get $user)) + i32.const 0 + i32.ne + (if (then (unreachable))) + + ;; set stack global + local.get $user + i32.const 1024 + call $set_stack + + ;; get stack + local.get $user + call $stack_left + i32.const 1024 + i32.ne + (if (then (unreachable))) + + ;; call a successful func in user.wat ($safe) + local.get $user + i32.const 1 ;; $safe + call $user_func + i32.const 1 + i32.ne + (if (then (unreachable))) + + ;; recover from an unreachable + local.get $user + i32.const 2 ;; $unreachable + call $user_func + i32.const 2 ;; indicates failure + i32.ne + (if (then (unreachable))) + + ;; push some items to the stack + i32.const 0xa4b0 + i64.const 0xa4b1 + i32.const 0xa4b2 + + ;; recover from an out-of-bounds memory access + local.get $user + i32.const 3 ;; $out_of_bounds + call $user_func + i32.const 2 ;; indicates failure + i32.ne + (if (then (unreachable))) + + ;; drop the items from the stack + drop + drop + drop + + ;; unlink module + call $unlink + call $halt + ) + (start $start) + (memory 1)) diff --git a/arbitrator/prover/test-cases/forward-test.wat b/arbitrator/prover/test-cases/forward-test.wat new file mode 100644 index 000000000..b9beff0d8 --- /dev/null +++ b/arbitrator/prover/test-cases/forward-test.wat @@ -0,0 +1,32 @@ + +(module + (import "forward" "add" (func $add (param i32 i32) (result i32))) + (import "forward" "sub" (func $sub (param i32 i32) (result i32))) + (import "forward" "mul" (func $mul (param i32 i32) (result i32))) + (func $start + ;; this address will update each time a forwarded call is made + i32.const 0xa4b + i32.const 805 + i32.store + + i32.const 11 + i32.const 5 + call $sub + + i32.const 3 + i32.const -2 + call $mul + + call $add + (if + (then (unreachable))) + + ;; check that the address has changed + i32.const 0xa4b + i32.load + i32.const 808 + i32.ne + (if + (then (unreachable)))) + (start $start) + (memory 1)) diff --git a/arbitrator/prover/test-cases/forward/forward.wat b/arbitrator/prover/test-cases/forward/forward.wat new file mode 100644 index 000000000..ff55953e6 --- /dev/null +++ b/arbitrator/prover/test-cases/forward/forward.wat @@ -0,0 +1,8 @@ + +(module + (import "target" "arbitrator_forward__add" (func $add (param i32 i32) (result i32))) + (import "target" "arbitrator_forward__sub" (func $sub (param i32 i32) (result i32))) + (import "target" "arbitrator_forward__mul" (func $mul (param i32 i32) (result i32))) + (export "forward__add" (func $add)) + (export "forward__sub" (func $sub)) + (export "forward__mul" (func $mul))) diff --git a/arbitrator/prover/test-cases/forward/target.wat b/arbitrator/prover/test-cases/forward/target.wat new file mode 100644 index 000000000..0779eb753 --- /dev/null +++ b/arbitrator/prover/test-cases/forward/target.wat @@ -0,0 +1,27 @@ + +(module + (import "env" "wavm_caller_load8" (func $load (param i32) (result i32))) + (import "env" "wavm_caller_store8" (func $store (param i32 i32))) + (func (export "target__add") (param i32 i32) (result i32) + call $write_caller + local.get 0 + local.get 1 + i32.add) + (func (export "target__sub") (param i32 i32) (result i32) + call $write_caller + local.get 0 + local.get 1 + i32.sub) + (func (export "target__mul") (param i32 i32) (result i32) + call $write_caller + local.get 0 + local.get 1 + i32.mul) + (func $write_caller (export "write_caller") + ;; increment the value at address 0xa4b in the caller + i32.const 0xa4b + i32.const 0xa4b + call $load + i32.const 1 + i32.add + call $store)) diff --git a/arbitrator/prover/test-cases/global-state-wavm.wat b/arbitrator/prover/test-cases/global-state-wavm.wat new file mode 100644 index 000000000..6ac2b0ee8 --- /dev/null +++ b/arbitrator/prover/test-cases/global-state-wavm.wat @@ -0,0 +1,23 @@ +(import "env" "wavm_set_globalstate_u64" (func $set (param i32) (param i64))) +(import "env" "wavm_get_globalstate_u64" (func $get (param i32) (result i64))) +(import "env" "wavm_halt_and_set_finished" (func $halt)) + +(func $entry + (i32.const 0) + (i64.const 10) + (call $set) + (loop + (i32.const 0) + (i32.const 0) + (call $get) + (i64.sub (i64.const 1)) + (call $set) + (i32.const 0) + (call $get) + (i32.wrap_i64) + (br_if 0) + ) + (call $halt) +) + +(start $entry) diff --git a/arbitrator/prover/test-cases/global-state-wrapper.wat b/arbitrator/prover/test-cases/global-state-wrapper.wat index a133467f7..8c7f30142 100644 --- a/arbitrator/prover/test-cases/global-state-wrapper.wat +++ b/arbitrator/prover/test-cases/global-state-wrapper.wat @@ -6,7 +6,7 @@ (import "env" "wavm_read_inbox_message" (func $readinbox (param i64) (param i32) (param i32) (result i32))) (import "env" "wavm_halt_and_set_finished" (func $halt)) -(export "env__wavm_set_globalstate_u64" (func $set)) -(export "env__wavm_get_globalstate_u64" (func $get)) -(export "env__wavm_read_inbox_message" (func $readinbox)) -(export "env__wavm_halt_and_set_finished" (func $halt)) +(export "wrapper__set_globalstate_u64" (func $set)) +(export "wrapper__get_globalstate_u64" (func $get)) +(export "wrapper__read_inbox_message" (func $readinbox)) +(export "wrapper__halt_and_set_finished" (func $halt)) diff --git a/arbitrator/prover/test-cases/global-state.wat b/arbitrator/prover/test-cases/global-state.wat index 6ac2b0ee8..6fc0c78b2 100644 --- a/arbitrator/prover/test-cases/global-state.wat +++ b/arbitrator/prover/test-cases/global-state.wat @@ -1,6 +1,6 @@ -(import "env" "wavm_set_globalstate_u64" (func $set (param i32) (param i64))) -(import "env" "wavm_get_globalstate_u64" (func $get (param i32) (result i64))) -(import "env" "wavm_halt_and_set_finished" (func $halt)) +(import "wrapper" "set_globalstate_u64" (func $set (param i32) (param i64))) +(import "wrapper" "get_globalstate_u64" (func $get (param i32) (result i64))) +(import "wrapper" "halt_and_set_finished" (func $halt)) (func $entry (i32.const 0) diff --git a/arbitrator/prover/test-cases/globals.wat b/arbitrator/prover/test-cases/globals.wat index a4b6bfd69..451b83a01 100644 --- a/arbitrator/prover/test-cases/globals.wat +++ b/arbitrator/prover/test-cases/globals.wat @@ -18,7 +18,9 @@ (drop) ) -(start 0) - - +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) +(start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/go/main.go b/arbitrator/prover/test-cases/go/main.go index cc6d954bd..d8be4945f 100644 --- a/arbitrator/prover/test-cases/go/main.go +++ b/arbitrator/prover/test-cases/go/main.go @@ -1,6 +1,9 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +//go:build wasm +// +build wasm + package main import ( @@ -11,6 +14,7 @@ import ( "math/big" "os" "runtime" + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -18,6 +22,7 @@ import ( merkletree "github.com/wealdtech/go-merkletree" "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/wavmio" ) @@ -48,7 +53,7 @@ func MerkleSample(data [][]byte, toproove int) (bool, error) { // Verify the proof for 'Baz' } -func testCompression(data []byte) { +func testCompression(data []byte, doneChan chan struct{}) { compressed, err := arbcompress.CompressLevel(data, 0) if err != nil { panic(err) @@ -60,6 +65,7 @@ func testCompression(data []byte) { if !bytes.Equal(decompressed, data) { panic("data differs after compression / decompression") } + doneChan <- struct{}{} } const FIELD_ELEMENTS_PER_BLOB = 4096 @@ -67,11 +73,51 @@ const BYTES_PER_FIELD_ELEMENT = 32 var BLS_MODULUS, _ = new(big.Int).SetString("52435875175126190479447740508185965837690552500527637822603658699938581184513", 10) +var stylusModuleHash = common.HexToHash("a149cf8113ff9c95f2c8c2a1423575367de86dd422d87114bb9ea47baf535dd7") // user.wat + +func callStylusProgram(recurse int) { + evmData := programs.EvmData{} + progParams := programs.ProgParams{ + MaxDepth: 10000, + InkPrice: 1, + DebugMode: true, + } + reqHandler := func(req programs.RequestType, input []byte) ([]byte, []byte, uint64) { + fmt.Printf("got request type %d req %v\n", req, input) + if req == programs.GetBytes32 { + if recurse > 0 { + callStylusProgram(recurse - 1) + } + answer := common.Hash{} + return answer[:], nil, 1 + } + + panic("unsupported call") + } + calldata := common.Hash{}.Bytes() + _, _, err := programs.CallProgramLoop( + stylusModuleHash, + calldata, + 160000000, + &evmData, + &progParams, + reqHandler) + if err != nil { + panic(err) + } +} + func main() { fmt.Printf("starting executable with %v arg(s): %v\n", len(os.Args), os.Args) runtime.GC() time.Sleep(time.Second) + fmt.Printf("Stylus test\n") + + callStylusProgram(5) + + fmt.Printf("Stylus test done!\n") + // Data for the tree data := [][]byte{ []byte("Foo"), @@ -79,34 +125,59 @@ func main() { []byte("Baz"), } - verified, err := MerkleSample(data, 0) - if err != nil { - panic(err) - } - if !verified { - panic("failed to verify proof for Baz") - } - verified, err = MerkleSample(data, 1) - if err != nil { - panic(err) - } - if !verified { - panic("failed to verify proof for Baz") - } + var wg sync.WaitGroup - verified, err = MerkleSample(data, -1) - if err != nil { - if verified { - panic("succeeded to verify proof invalid") + wg.Add(1) + go func() { + verified, err := MerkleSample(data, 0) + if err != nil { + panic(err) } - } + if !verified { + panic("failed to verify proof for Baz") + } + wg.Done() + }() + wg.Add(1) + go func() { + verified, err := MerkleSample(data, 1) + if err != nil { + panic(err) + } + if !verified { + panic("failed to verify proof for Baz") + } + wg.Done() + }() + wg.Add(1) + go func() { + verified, err := MerkleSample(data, -1) + if err != nil { + if verified { + panic("succeeded to verify proof invalid") + } + } + wg.Done() + }() + wg.Wait() + println("verified proofs with waitgroup!\n") - println("verified both proofs!\n") + doneChan1 := make(chan struct{}) + doneChan2 := make(chan struct{}) + go testCompression([]byte{}, doneChan1) + go testCompression([]byte("This is a test string la la la la la la la la la la"), doneChan2) + <-doneChan2 + <-doneChan1 - testCompression([]byte{}) - testCompression([]byte("This is a test string la la la la la la la la la la")) + println("compression + chan test passed!\n") - println("test compression passed!\n") + if wavmio.GetInboxPosition() != 0 { + panic("unexpected inbox pos") + } + if wavmio.GetLastBlockHash() != (common.Hash{}) { + panic("unexpected lastblock hash") + } + println("wavmio test passed!\n") checkPreimage := func(ty arbutil.PreimageType, hash common.Hash) { preimage, err := wavmio.ResolveTypedPreimage(ty, hash) diff --git a/arbitrator/prover/test-cases/if-else.wat b/arbitrator/prover/test-cases/if-else.wat index 252a3be75..6a2d3a5bc 100644 --- a/arbitrator/prover/test-cases/if-else.wat +++ b/arbitrator/prover/test-cases/if-else.wat @@ -18,4 +18,9 @@ (drop) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/iops.wat b/arbitrator/prover/test-cases/iops.wat index 7ec8ab945..906ae4362 100644 --- a/arbitrator/prover/test-cases/iops.wat +++ b/arbitrator/prover/test-cases/iops.wat @@ -80,4 +80,9 @@ (drop) ) -(start 0) \ No newline at end of file +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + +(start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/link.txt b/arbitrator/prover/test-cases/link.txt new file mode 100644 index 000000000..368e40b40 --- /dev/null +++ b/arbitrator/prover/test-cases/link.txt @@ -0,0 +1,13 @@ +block +call +call-indirect +const +div-overflow +globals +if-else +locals +loop +math +iops +user +return diff --git a/arbitrator/prover/test-cases/link.wat b/arbitrator/prover/test-cases/link.wat new file mode 100644 index 000000000..ef1532648 --- /dev/null +++ b/arbitrator/prover/test-cases/link.wat @@ -0,0 +1,89 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "hostio" "wavm_link_module" (func $link (param i32) (result i32))) + (import "hostio" "wavm_unlink_module" (func $unlink (param) (result))) + (import "env" "wavm_halt_and_set_finished" (func $halt )) + + ;; WAVM module hashes + (data (i32.const 0x000) + "\eb\12\b0\76\57\15\ad\16\0a\78\54\4d\c7\8d\d4\86\1c\58\a3\ee\77\f9\4a\4e\61\a3\f1\7f\d9\d2\be\8a") ;; block + (data (i32.const 0x020) + "\01\90\21\0c\1d\c8\45\9c\32\ef\a6\00\44\3b\e0\b6\31\70\1f\ce\7a\38\90\1c\e0\c5\40\6d\d8\ce\30\a6") ;; call + (data (i32.const 0x040) + "\e1\a2\fa\8e\81\2a\34\2e\cf\0f\62\46\ba\a4\45\8e\2d\95\2f\ec\1e\79\8f\dc\1b\1c\b8\15\cf\26\02\6c") ;; indirect + (data (i32.const 0x060) + "\ae\cb\eb\e9\0b\5e\1f\78\1b\66\5b\ff\8a\a4\18\a1\a2\e9\90\26\8b\df\df\95\64\54\82\07\6a\d4\e6\20") ;; const + (data (i32.const 0x080) + "\8b\7b\7e\a8\b8\21\c8\d0\2a\80\7c\1e\4b\6d\0d\07\f3\2d\8b\4e\f1\6b\e4\44\03\cf\05\66\9b\09\be\6d") ;; div + (data (i32.const 0x0a0) + "\da\4a\41\74\d6\2e\20\36\8e\cb\8e\5d\45\12\1c\28\1d\c4\8f\1d\77\92\9f\07\a8\6b\35\ea\89\2e\f9\72") ;; globals + (data (i32.const 0x0c0) + "\3f\ec\7c\06\04\b2\0d\99\bb\10\85\61\91\ea\b6\97\a7\a2\d1\19\67\2e\7c\d9\17\d4\6b\45\e8\4b\83\4b") ;; if-else + (data (i32.const 0x0e0) + "\30\12\24\71\df\9f\a9\f8\9c\33\9b\37\a7\08\f5\aa\5f\53\68\b4\e4\de\66\bb\73\ff\30\29\47\5f\50\e5") ;; locals + (data (i32.const 0x100) + "\f3\95\dd\a7\e1\d7\df\94\06\ca\93\0f\53\bf\66\ce\1a\aa\b2\30\68\08\64\b5\5b\61\54\2c\1d\62\e8\25") ;; loop + (data (i32.const 0x120) + "\8c\a3\63\7c\4e\70\f7\79\13\0c\9a\94\5e\63\3b\a9\06\80\9f\a6\51\0e\32\34\e0\9d\78\05\6a\30\98\0f") ;; math + (data (i32.const 0x140) + "\47\f7\4f\9c\21\51\4f\52\24\ea\d3\37\5c\bf\a9\1b\1a\5f\ef\22\a5\2a\60\30\c5\52\18\90\6b\b1\51\e5") ;; iops + (data (i32.const 0x160) + "\a1\49\cf\81\13\ff\9c\95\f2\c8\c2\a1\42\35\75\36\7d\e8\6d\d4\22\d8\71\14\bb\9e\a4\7b\af\53\5d\d7") ;; user + (data (i32.const 0x180) + "\ee\47\08\f6\47\b2\10\88\1f\89\86\e7\e3\79\6b\b2\77\43\f1\4e\ee\cf\45\4a\9b\7c\d7\c4\5b\63\b6\d7") ;; return + + (func $start (local $counter i32) + + ;; add modules + (loop $top + ;; increment counter + local.get $counter + local.get $counter + i32.const 1 + i32.add + local.set $counter + + ;; link module with unique hash + i32.const 32 + i32.mul + call $link + + ;; loop until 12 modules + i32.const 12 + i32.le_s + br_if $top + ) + + ;; reset counter + i32.const 0 + local.set $counter + + ;; link and unlink modules + (loop $top + ;; increment counter + local.get $counter + local.get $counter + i32.const 1 + i32.add + local.set $counter + + ;; unlink 2 modules + call $unlink + call $unlink + + ;; link module with unique hash + i32.const 32 + i32.mul + call $link + + ;; loop until most are gone + i32.const 3 + i32.ge_s + br_if $top) + + call $halt + ) + (memory 1) + (start $start)) diff --git a/arbitrator/prover/test-cases/locals.wat b/arbitrator/prover/test-cases/locals.wat index 3e41faa27..01b91937c 100644 --- a/arbitrator/prover/test-cases/locals.wat +++ b/arbitrator/prover/test-cases/locals.wat @@ -16,5 +16,9 @@ (drop) ) -(start 0) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) +(start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/loop.wat b/arbitrator/prover/test-cases/loop.wat index 34cdb77da..4c32d6a5b 100644 --- a/arbitrator/prover/test-cases/loop.wat +++ b/arbitrator/prover/test-cases/loop.wat @@ -29,6 +29,9 @@ (unreachable) ) -(start 0) - +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) +(start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/math.wat b/arbitrator/prover/test-cases/math.wat index 7315e2d71..2d78dbeb5 100644 --- a/arbitrator/prover/test-cases/math.wat +++ b/arbitrator/prover/test-cases/math.wat @@ -81,4 +81,9 @@ (drop) ) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) + (start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/read-inboxmsg-10.wat b/arbitrator/prover/test-cases/read-inboxmsg-10.wat index a7977e8e7..3c1badc44 100644 --- a/arbitrator/prover/test-cases/read-inboxmsg-10.wat +++ b/arbitrator/prover/test-cases/read-inboxmsg-10.wat @@ -1,7 +1,7 @@ -(import "env" "wavm_set_globalstate_u64" (func $set (param i32) (param i64))) -(import "env" "wavm_get_globalstate_u64" (func $get (param i32) (result i64))) -(import "env" "wavm_read_inbox_message" (func $readinbox (param i64) (param i32) (param i32) (result i32))) -(import "env" "wavm_halt_and_set_finished" (func $halt)) +(import "wrapper" "set_globalstate_u64" (func $set (param i32) (param i64))) +(import "wrapper" "get_globalstate_u64" (func $get (param i32) (result i64))) +(import "wrapper" "read_inbox_message" (func $readinbox (param i64) (param i32) (param i32) (result i32))) +(import "wrapper" "halt_and_set_finished" (func $halt)) (memory 1) diff --git a/arbitrator/prover/test-cases/return.wat b/arbitrator/prover/test-cases/return.wat index f2d36f8e8..278b1651f 100644 --- a/arbitrator/prover/test-cases/return.wat +++ b/arbitrator/prover/test-cases/return.wat @@ -20,5 +20,9 @@ ) ) -(start 0) +(func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) +) +(start 0) +(memory (export "memory") 0 0) diff --git a/arbitrator/prover/test-cases/user.wat b/arbitrator/prover/test-cases/user.wat new file mode 100644 index 000000000..9ecb4dcc4 --- /dev/null +++ b/arbitrator/prover/test-cases/user.wat @@ -0,0 +1,53 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "storage_load_bytes32" (func $storage_load_bytes32 (param i32 i32))) + + (func $storage_load (result i32) + i32.const 0 + i32.const 32 + call $storage_load_bytes32 + i32.const 0 + ) + (func $safe (result i32) + i32.const 5 + ) + (func $unreachable (result i32) + i32.const 0 + i64.const 4 + unreachable + ) + (func $out_of_bounds (result i32) + i32.const 0xFFFFFF + i32.load + ) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + ;; this func uses $args_len to select which func to call + + ;; only call that succeeds + (i32.eq (local.get $args_len) (i32.const 1)) + (if + (then (call $safe) (return)) + ) + + ;; reverts due to an unreachable + (i32.eq (local.get $args_len) (i32.const 2)) + (if + (then (call $unreachable) (return)) + ) + + ;; reverts due to an out of bounds memory access + (i32.eq (local.get $args_len) (i32.const 3)) + (if + (then (call $out_of_bounds) (return)) + ) + + (i32.eq (local.get $args_len) (i32.const 32)) + (if + (then (call $storage_load) (return)) + ) + + unreachable + ) + (memory (export "memory") 1 1)) diff --git a/arbitrator/stylus/Cargo.toml b/arbitrator/stylus/Cargo.toml new file mode 100644 index 000000000..4717bd631 --- /dev/null +++ b/arbitrator/stylus/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "stylus" +version = "0.1.0" +edition = "2021" + +[dependencies] +arbutil = { path = "../arbutil/" } +brotli = { path = "../brotli" } +caller-env = { path = "../caller-env", features = ["wasmer_traits"] } +prover = { path = "../prover/", default-features = false, features = ["native"] } +wasmer = { path = "../tools/wasmer/lib/api" } +wasmer-vm = { path = "../tools/wasmer/lib/vm/" } +wasmer-types = { path = "../tools/wasmer/lib/types/" } +wasmer-compiler-singlepass = { path = "../tools/wasmer/lib/compiler-singlepass", default-features = false, features = ["std", "unwind", "avx"] } +wasmer-compiler-cranelift = { path = "../tools/wasmer/lib/compiler-cranelift" } +wasmer-compiler-llvm = { path = "../tools/wasmer/lib/compiler-llvm", optional = true } +user-host-trait = { path = "../wasm-libraries/user-host-trait/" } +derivative = "2.2.0" +parking_lot = "0.12.1" +thiserror = "1.0.33" +bincode = "1.3.3" +lazy_static.workspace = true +libc = "0.2.108" +lru.workspace = true +eyre = "0.6.5" +rand = "0.8.5" +fnv = "1.0.7" +hex = "0.4.3" + +[dev-dependencies] +num-bigint = "0.4.4" + +[features] +default = ["rayon", "singlepass_rayon"] +llvm = ["dep:wasmer-compiler-llvm"] +benchmark = [] +timings = [] +singlepass_rayon = ["prover/singlepass_rayon", "wasmer-compiler-singlepass/rayon"] +rayon = ["prover/rayon"] + +[lib] +crate-type = ["lib", "staticlib"] diff --git a/arbitrator/stylus/cbindgen.toml b/arbitrator/stylus/cbindgen.toml new file mode 100644 index 000000000..95adfd462 --- /dev/null +++ b/arbitrator/stylus/cbindgen.toml @@ -0,0 +1,13 @@ +language = "C" +include_guard = "arbitrator_bindings" + +[parse] +parse_deps = true +include = ["arbutil", "prover", "brotli"] +extra_bindings = ["arbutil", "prover", "brotli"] + +[enum] +prefix_with_name = true + +[export] +include = ["EvmApiMethod", "EvmApiStatus"] diff --git a/arbitrator/stylus/src/benchmarks.rs b/arbitrator/stylus/src/benchmarks.rs new file mode 100644 index 000000000..d8d558d9e --- /dev/null +++ b/arbitrator/stylus/src/benchmarks.rs @@ -0,0 +1,92 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::{env::WasmEnv, native::NativeInstance}; +use arbutil::{crypto, format}; +use eyre::Result; +use prover::programs::{config::StylusConfig, STYLUS_ENTRY_POINT}; +use std::time::{Duration, Instant}; +use wasmer::{CompilerConfig, Imports, Instance, Module, Store}; +use wasmer_compiler_cranelift::{Cranelift, CraneliftOptLevel}; +use wasmer_compiler_singlepass::Singlepass; + +#[cfg(feature = "llvm")] +use wasmer_compiler_llvm::{LLVMOptLevel, LLVM}; + +#[test] +fn benchmark_wasmer() -> Result<()> { + // benchmarks wasmer across all compiler backends + + fn single() -> Store { + let mut compiler = Singlepass::new(); + compiler.canonicalize_nans(true); + compiler.enable_verifier(); + Store::new(compiler) + } + + fn cranelift() -> Store { + let mut compiler = Cranelift::new(); + compiler.canonicalize_nans(true); + compiler.enable_verifier(); + compiler.opt_level(CraneliftOptLevel::Speed); + Store::new(compiler) + } + + #[cfg(feature = "llvm")] + fn llvm() -> Store { + let mut compiler = LLVM::new(); + compiler.canonicalize_nans(true); + compiler.enable_verifier(); + compiler.opt_level(LLVMOptLevel::Aggressive); + Store::new(compiler) + } + + fn emulated(mut store: Store) -> Result { + let file = "tests/keccak-100/target/wasm32-unknown-unknown/release/keccak-100.wasm"; + let wasm = std::fs::read(file)?; + let module = Module::new(&mut store, &wasm)?; + let instance = Instance::new(&mut store, &module, &Imports::new())?; + + let exports = instance.exports; + let main = exports.get_typed_function::<(i32, i32), i32>(&store, "main")?; + + let time = Instant::now(); + main.call(&mut store, 0, 0)?; + Ok(time.elapsed()) + } + + fn stylus() -> Result { + let mut args = vec![100]; // 100 keccaks + args.extend([0; 32]); + + let config = StylusConfig::default(); + let env = WasmEnv::new(config, args); + + let file = "tests/keccak/target/wasm32-unknown-unknown/release/keccak.wasm"; + let mut instance = NativeInstance::from_path(file, env)?; + let exports = &instance.exports; + let main = exports.get_typed_function::(&instance.store, STYLUS_ENTRY_POINT)?; + + let time = Instant::now(); + main.call(&mut instance.store, 1)?; + Ok(time.elapsed()) + } + + fn native() -> Duration { + let time = Instant::now(); + let mut data = [0; 32]; + for _ in 0..100 { + data = crypto::keccak(&data); + } + assert_ne!(data, [0; 32]); // keeps the optimizer from pruning `data` + time.elapsed() + } + + println!("Native: {}", format::time(native())); + #[cfg(feature = "llvm")] + println!("LLVM: {}", format::time(emulated(llvm())?)); + println!("Crane: {}", format::time(emulated(cranelift())?)); + println!("Single: {}", format::time(emulated(single())?)); + println!("Stylus: {}", format::time(stylus()?)); + Ok(()) +} diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs new file mode 100644 index 000000000..06739f221 --- /dev/null +++ b/arbitrator/stylus/src/cache.rs @@ -0,0 +1,159 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use arbutil::Bytes32; +use eyre::Result; +use lazy_static::lazy_static; +use lru::LruCache; +use parking_lot::Mutex; +use prover::programs::config::CompileConfig; +use std::{collections::HashMap, num::NonZeroUsize}; +use wasmer::{Engine, Module, Store}; + +lazy_static! { + static ref INIT_CACHE: Mutex = Mutex::new(InitCache::new(256)); +} + +macro_rules! cache { + () => { + INIT_CACHE.lock() + }; +} + +pub struct InitCache { + long_term: HashMap, + lru: LruCache, +} + +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +struct CacheKey { + module_hash: Bytes32, + version: u16, + debug: bool, +} + +impl CacheKey { + fn new(module_hash: Bytes32, version: u16, debug: bool) -> Self { + Self { + module_hash, + version, + debug, + } + } +} + +#[derive(Clone)] +struct CacheItem { + module: Module, + engine: Engine, +} + +impl CacheItem { + fn new(module: Module, engine: Engine) -> Self { + Self { module, engine } + } + + fn data(&self) -> (Module, Store) { + (self.module.clone(), Store::new(self.engine.clone())) + } +} + +impl InitCache { + // current implementation only has one tag that stores to the long_term + // future implementations might have more, but 0 is a reserved tag + // that will never modify long_term state + const ARBOS_TAG: u32 = 1; + + fn new(size: usize) -> Self { + Self { + long_term: HashMap::new(), + lru: LruCache::new(NonZeroUsize::new(size).unwrap()), + } + } + + pub fn set_lru_size(size: u32) { + cache!() + .lru + .resize(NonZeroUsize::new(size.try_into().unwrap()).unwrap()) + } + + /// Retrieves a cached value, updating items as necessary. + pub fn get(module_hash: Bytes32, version: u16, debug: bool) -> Option<(Module, Store)> { + let mut cache = cache!(); + let key = CacheKey::new(module_hash, version, debug); + + // See if the item is in the long term cache + if let Some(item) = cache.long_term.get(&key) { + return Some(item.data()); + } + + // See if the item is in the LRU cache, promoting if so + if let Some(item) = cache.lru.get(&key) { + return Some(item.data()); + } + None + } + + /// Inserts an item into the long term cache, cloning from the LRU cache if able. + /// If long_term_tag is 0 will only insert to LRU + pub fn insert( + module_hash: Bytes32, + module: &[u8], + version: u16, + long_term_tag: u32, + debug: bool, + ) -> Result<(Module, Store)> { + let key = CacheKey::new(module_hash, version, debug); + + // if in LRU, add to ArbOS + let mut cache = cache!(); + if let Some(item) = cache.long_term.get(&key) { + return Ok(item.data()); + } + if let Some(item) = cache.lru.peek(&key).cloned() { + if long_term_tag == Self::ARBOS_TAG { + cache.long_term.insert(key, item.clone()); + } else { + cache.lru.promote(&key) + } + return Ok(item.data()); + } + drop(cache); + + let engine = CompileConfig::version(version, debug).engine(); + let module = unsafe { Module::deserialize_unchecked(&engine, module)? }; + + let item = CacheItem::new(module, engine); + let data = item.data(); + let mut cache = cache!(); + if long_term_tag != Self::ARBOS_TAG { + cache.lru.put(key, item); + } else { + cache.long_term.insert(key, item); + } + Ok(data) + } + + /// Evicts an item in the long-term cache. + pub fn evict(module_hash: Bytes32, version: u16, long_term_tag: u32, debug: bool) { + if long_term_tag != Self::ARBOS_TAG { + return; + } + let key = CacheKey::new(module_hash, version, debug); + let mut cache = cache!(); + if let Some(item) = cache.long_term.remove(&key) { + cache.lru.put(key, item); + } + } + + pub fn clear_long_term(long_term_tag: u32) { + if long_term_tag != Self::ARBOS_TAG { + return; + } + let mut cache = cache!(); + let cache = &mut *cache; + for (key, item) in cache.long_term.drain() { + cache.lru.put(key, item); // not all will fit, just a heuristic + } + } +} diff --git a/arbitrator/stylus/src/env.rs b/arbitrator/stylus/src/env.rs new file mode 100644 index 000000000..69d542070 --- /dev/null +++ b/arbitrator/stylus/src/env.rs @@ -0,0 +1,259 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use arbutil::{ + evm::{ + api::{DataReader, EvmApi}, + EvmData, + }, + pricing, +}; +use caller_env::GuestPtr; +use derivative::Derivative; +use eyre::{eyre, ErrReport}; +use prover::programs::{config::PricingParams, meter::OutOfInkError, prelude::*}; +use std::{ + fmt::Debug, + io, + marker::PhantomData, + mem::MaybeUninit, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; +use thiserror::Error; +use wasmer::{FunctionEnvMut, Memory, MemoryAccessError, MemoryView, Pages, StoreMut}; +use wasmer_types::RawValue; +use wasmer_vm::VMGlobalDefinition; + +pub type WasmEnvMut<'a, D, E> = FunctionEnvMut<'a, WasmEnv>; + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct WasmEnv> { + /// The instance's arguments + #[derivative(Debug(format_with = "arbutil::format::hex_fmt"))] + pub args: Vec, + /// The instance's return data + #[derivative(Debug(format_with = "arbutil::format::hex_fmt"))] + pub outs: Vec, + /// Mechanism for reading and writing the module's memory + pub memory: Option, + /// Mechanism for accessing metering-specific global state + pub meter: Option, + /// Mechanism for reading and writing permanent storage, and doing calls + pub evm_api: E, + /// Mechanism for reading EVM context data + pub evm_data: EvmData, + /// The compile time config + pub compile: CompileConfig, + /// The runtime config + pub config: Option, + // Using the unused generic parameter D in a PhantomData field + _data_reader_marker: PhantomData, +} + +impl> WasmEnv { + pub fn new( + compile: CompileConfig, + config: Option, + evm_api: E, + evm_data: EvmData, + ) -> Self { + Self { + compile, + config, + evm_api, + evm_data, + args: vec![], + outs: vec![], + memory: None, + meter: None, + _data_reader_marker: PhantomData, + } + } + + pub fn start<'a>( + env: &'a mut WasmEnvMut<'_, D, E>, + ink: u64, + ) -> Result, Escape> { + let mut info = Self::program(env)?; + info.buy_ink(pricing::HOSTIO_INK + ink)?; + Ok(info) + } + + pub fn program<'a>(env: &'a mut WasmEnvMut<'_, D, E>) -> Result, Escape> { + let (env, store) = env.data_and_store_mut(); + let memory = env.memory.clone().unwrap(); + let mut info = HostioInfo { + env, + memory, + store, + start_ink: 0, + }; + if info.env.evm_data.tracing { + info.start_ink = info.ink_ready()?; + } + Ok(info) + } + + pub fn meter_mut(&mut self) -> &mut MeterData { + self.meter.as_mut().expect("not metered") + } + + pub fn meter(&self) -> &MeterData { + self.meter.as_ref().expect("not metered") + } +} + +#[derive(Clone, Copy, Debug)] +pub struct MeterData { + /// The amount of ink left + pub ink_left: NonNull, + /// Whether the instance has run out of ink + pub ink_status: NonNull, +} + +impl MeterData { + pub fn ink(&self) -> u64 { + unsafe { self.ink_left.as_ref().val.u64 } + } + + pub fn status(&self) -> u32 { + unsafe { self.ink_status.as_ref().val.u32 } + } + + pub fn set_ink(&mut self, ink: u64) { + unsafe { self.ink_left.as_mut().val = RawValue { u64: ink } } + } + + pub fn set_status(&mut self, status: u32) { + unsafe { self.ink_status.as_mut().val = RawValue { u32: status } } + } +} + +/// The data we're pointing to is owned by the `NativeInstance`. +/// These are simple integers whose lifetime is that of the instance. +/// Stylus is also single-threaded. +unsafe impl Send for MeterData {} + +pub struct HostioInfo<'a, D: DataReader, E: EvmApi> { + pub env: &'a mut WasmEnv, + pub memory: Memory, + pub store: StoreMut<'a>, + pub start_ink: u64, +} + +impl<'a, D: DataReader, E: EvmApi> HostioInfo<'a, D, E> { + pub fn config(&self) -> StylusConfig { + self.config.expect("no config") + } + + pub fn pricing(&self) -> PricingParams { + self.config().pricing + } + + pub fn view(&self) -> MemoryView { + self.memory.view(&self.store) + } + + pub fn memory_size(&self) -> Pages { + self.memory.ty(&self.store).minimum + } + + // TODO: use the unstable array_assum_init + pub fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], MemoryAccessError> { + let mut data = [MaybeUninit::uninit(); N]; + self.view().read_uninit(ptr.into(), &mut data)?; + Ok(data.map(|x| unsafe { x.assume_init() })) + } +} + +impl<'a, D: DataReader, E: EvmApi> MeteredMachine for HostioInfo<'a, D, E> { + fn ink_left(&self) -> MachineMeter { + let vm = self.env.meter(); + match vm.status() { + 0_u32 => MachineMeter::Ready(vm.ink()), + _ => MachineMeter::Exhausted, + } + } + + fn set_meter(&mut self, meter: MachineMeter) { + let vm = self.env.meter_mut(); + vm.set_ink(meter.ink()); + vm.set_status(meter.status()); + } +} + +impl<'a, D: DataReader, E: EvmApi> GasMeteredMachine for HostioInfo<'a, D, E> { + fn pricing(&self) -> PricingParams { + self.config().pricing + } +} + +impl<'a, D: DataReader, E: EvmApi> Deref for HostioInfo<'a, D, E> { + type Target = WasmEnv; + + fn deref(&self) -> &Self::Target { + self.env + } +} + +impl<'a, D: DataReader, E: EvmApi> DerefMut for HostioInfo<'a, D, E> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.env + } +} + +pub type MaybeEscape = Result<(), Escape>; + +#[derive(Error, Debug)] +pub enum Escape { + #[error("failed to access memory: `{0}`")] + Memory(MemoryAccessError), + #[error("internal error: `{0}`")] + Internal(ErrReport), + #[error("logic error: `{0}`")] + Logical(ErrReport), + #[error("out of ink")] + OutOfInk, + #[error("exit early: `{0}`")] + Exit(u32), +} + +impl Escape { + pub fn _internal(error: &'static str) -> Result { + Err(Self::Internal(eyre!(error))) + } + + pub fn logical(error: &'static str) -> Result { + Err(Self::Logical(eyre!(error))) + } + + pub fn out_of_ink() -> Result { + Err(Self::OutOfInk) + } +} + +impl From for Escape { + fn from(_: OutOfInkError) -> Self { + Self::OutOfInk + } +} + +impl From for Escape { + fn from(err: MemoryAccessError) -> Self { + Self::Memory(err) + } +} + +impl From for Escape { + fn from(err: io::Error) -> Self { + Self::Internal(eyre!(err)) + } +} + +impl From for Escape { + fn from(err: ErrReport) -> Self { + Self::Internal(err) + } +} diff --git a/arbitrator/stylus/src/evm_api.rs b/arbitrator/stylus/src/evm_api.rs new file mode 100644 index 000000000..d26737282 --- /dev/null +++ b/arbitrator/stylus/src/evm_api.rs @@ -0,0 +1,50 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{GoSliceData, RustSlice}; +use arbutil::evm::{ + api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET}, + req::RequestHandler, +}; + +#[repr(C)] +pub struct NativeRequestHandler { + pub handle_request_fptr: unsafe extern "C" fn( + id: usize, + req_type: u32, + data: *mut RustSlice, + gas_cost: *mut u64, + result: *mut GoSliceData, + raw_data: *mut GoSliceData, + ), + pub id: usize, +} + +macro_rules! ptr { + ($expr:expr) => { + &mut $expr as *mut _ + }; +} + +impl RequestHandler for NativeRequestHandler { + fn request( + &mut self, + req_type: EvmApiMethod, + req_data: impl AsRef<[u8]>, + ) -> (Vec, GoSliceData, u64) { + let mut result = GoSliceData::null(); + let mut raw_data = GoSliceData::null(); + let mut cost = 0; + unsafe { + (self.handle_request_fptr)( + self.id, + req_type as u32 + EVM_API_METHOD_REQ_OFFSET, + ptr!(RustSlice::new(req_data.as_ref())), + ptr!(cost), + ptr!(result), + ptr!(raw_data), + ) + }; + (result.slice().to_vec(), raw_data, cost) + } +} diff --git a/arbitrator/stylus/src/host.rs b/arbitrator/stylus/src/host.rs new file mode 100644 index 000000000..7854386e2 --- /dev/null +++ b/arbitrator/stylus/src/host.rs @@ -0,0 +1,466 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::env::{Escape, HostioInfo, MaybeEscape, WasmEnv, WasmEnvMut}; +use arbutil::{ + evm::{ + api::{DataReader, EvmApi}, + EvmData, + }, + Color, +}; +use caller_env::GuestPtr; +use eyre::Result; +use prover::value::Value; +use std::{ + fmt::Display, + mem::{self, MaybeUninit}, +}; +use user_host_trait::UserHost; +use wasmer::{MemoryAccessError, WasmPtr}; + +impl<'a, DR, A> UserHost for HostioInfo<'a, DR, A> +where + DR: DataReader, + A: EvmApi, +{ + type Err = Escape; + type MemoryErr = MemoryAccessError; + type A = A; + + fn args(&self) -> &[u8] { + &self.args + } + + fn outs(&mut self) -> &mut Vec { + &mut self.outs + } + + fn evm_api(&mut self) -> &mut Self::A { + &mut self.evm_api + } + + fn evm_data(&self) -> &EvmData { + &self.evm_data + } + + fn evm_return_data_len(&mut self) -> &mut u32 { + &mut self.evm_data.return_data_len + } + + fn read_fixed( + &self, + ptr: GuestPtr, + ) -> std::result::Result<[u8; N], Self::MemoryErr> { + HostioInfo::read_fixed(self, ptr) + } + + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, Self::MemoryErr> { + let len = len as usize; + let mut data: Vec> = Vec::with_capacity(len); + // SAFETY: read_uninit fills all available space + unsafe { + data.set_len(len); + self.view().read_uninit(ptr.into(), &mut data)?; + Ok(mem::transmute(data)) + } + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), Self::MemoryErr> { + let ptr: WasmPtr = WasmPtr::new(ptr.into()); + ptr.deref(&self.view()).write(x)?; + Ok(()) + } + + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), Self::MemoryErr> { + self.view().write(ptr.into(), src) + } + + fn say(&self, text: D) { + println!("{} {text}", "Stylus says:".yellow()); + } + + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64) { + let start_ink = self.start_ink; + self.evm_api + .capture_hostio(name, args, outs, start_ink, end_ink); + } +} + +macro_rules! hostio { + ($env:expr, $($func:tt)*) => { + WasmEnv::program(&mut $env)?.$($func)* + }; +} + +pub(crate) fn read_args>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, read_args(ptr)) +} + +pub(crate) fn write_result>( + mut env: WasmEnvMut, + ptr: GuestPtr, + len: u32, +) -> MaybeEscape { + hostio!(env, write_result(ptr, len)) +} + +pub(crate) fn exit_early>( + mut env: WasmEnvMut, + status: u32, +) -> MaybeEscape { + hostio!(env, exit_early(status))?; + Err(Escape::Exit(status)) +} + +pub(crate) fn storage_load_bytes32>( + mut env: WasmEnvMut, + key: GuestPtr, + dest: GuestPtr, +) -> MaybeEscape { + hostio!(env, storage_load_bytes32(key, dest)) +} + +pub(crate) fn storage_cache_bytes32>( + mut env: WasmEnvMut, + key: GuestPtr, + value: GuestPtr, +) -> MaybeEscape { + hostio!(env, storage_cache_bytes32(key, value)) +} + +pub(crate) fn storage_flush_cache>( + mut env: WasmEnvMut, + clear: u32, +) -> MaybeEscape { + hostio!(env, storage_flush_cache(clear != 0)) +} + +pub(crate) fn transient_load_bytes32>( + mut env: WasmEnvMut, + key: GuestPtr, + dest: GuestPtr, +) -> MaybeEscape { + hostio!(env, transient_load_bytes32(key, dest)) +} + +pub(crate) fn transient_store_bytes32>( + mut env: WasmEnvMut, + key: GuestPtr, + value: GuestPtr, +) -> MaybeEscape { + hostio!(env, transient_store_bytes32(key, value)) +} + +pub(crate) fn call_contract>( + mut env: WasmEnvMut, + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + value: GuestPtr, + gas: u64, + ret_len: GuestPtr, +) -> Result { + hostio!( + env, + call_contract(contract, data, data_len, value, gas, ret_len) + ) +} + +pub(crate) fn delegate_call_contract>( + mut env: WasmEnvMut, + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> Result { + hostio!( + env, + delegate_call_contract(contract, data, data_len, gas, ret_len) + ) +} + +pub(crate) fn static_call_contract>( + mut env: WasmEnvMut, + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> Result { + hostio!( + env, + static_call_contract(contract, data, data_len, gas, ret_len) + ) +} + +pub(crate) fn create1>( + mut env: WasmEnvMut, + code: GuestPtr, + code_len: u32, + endowment: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) -> MaybeEscape { + hostio!( + env, + create1(code, code_len, endowment, contract, revert_len) + ) +} + +pub(crate) fn create2>( + mut env: WasmEnvMut, + code: GuestPtr, + code_len: u32, + endowment: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) -> MaybeEscape { + hostio!( + env, + create2(code, code_len, endowment, salt, contract, revert_len) + ) +} + +pub(crate) fn read_return_data>( + mut env: WasmEnvMut, + dest: GuestPtr, + offset: u32, + size: u32, +) -> Result { + hostio!(env, read_return_data(dest, offset, size)) +} + +pub(crate) fn return_data_size>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, return_data_size()) +} + +pub(crate) fn emit_log>( + mut env: WasmEnvMut, + data: GuestPtr, + len: u32, + topics: u32, +) -> MaybeEscape { + hostio!(env, emit_log(data, len, topics)) +} + +pub(crate) fn account_balance>( + mut env: WasmEnvMut, + address: GuestPtr, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, account_balance(address, ptr)) +} + +pub(crate) fn account_code>( + mut env: WasmEnvMut, + address: GuestPtr, + offset: u32, + size: u32, + code: GuestPtr, +) -> Result { + hostio!(env, account_code(address, offset, size, code)) +} + +pub(crate) fn account_code_size>( + mut env: WasmEnvMut, + address: GuestPtr, +) -> Result { + hostio!(env, account_code_size(address)) +} + +pub(crate) fn account_codehash>( + mut env: WasmEnvMut, + address: GuestPtr, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, account_codehash(address, ptr)) +} + +pub(crate) fn block_basefee>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, block_basefee(ptr)) +} + +pub(crate) fn block_coinbase>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, block_coinbase(ptr)) +} + +pub(crate) fn block_gas_limit>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, block_gas_limit()) +} + +pub(crate) fn block_number>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, block_number()) +} + +pub(crate) fn block_timestamp>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, block_timestamp()) +} + +pub(crate) fn chainid>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, chainid()) +} + +pub(crate) fn contract_address>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, contract_address(ptr)) +} + +pub(crate) fn evm_gas_left>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, evm_gas_left()) +} + +pub(crate) fn evm_ink_left>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, evm_ink_left()) +} + +pub(crate) fn math_div>( + mut env: WasmEnvMut, + value: GuestPtr, + divisor: GuestPtr, +) -> MaybeEscape { + hostio!(env, math_div(value, divisor)) +} + +pub(crate) fn math_mod>( + mut env: WasmEnvMut, + value: GuestPtr, + modulus: GuestPtr, +) -> MaybeEscape { + hostio!(env, math_mod(value, modulus)) +} + +pub(crate) fn math_pow>( + mut env: WasmEnvMut, + value: GuestPtr, + exponent: GuestPtr, +) -> MaybeEscape { + hostio!(env, math_pow(value, exponent)) +} + +pub(crate) fn math_add_mod>( + mut env: WasmEnvMut, + value: GuestPtr, + addend: GuestPtr, + modulus: GuestPtr, +) -> MaybeEscape { + hostio!(env, math_add_mod(value, addend, modulus)) +} + +pub(crate) fn math_mul_mod>( + mut env: WasmEnvMut, + value: GuestPtr, + multiplier: GuestPtr, + modulus: GuestPtr, +) -> MaybeEscape { + hostio!(env, math_mul_mod(value, multiplier, modulus)) +} + +pub(crate) fn msg_reentrant>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, msg_reentrant()) +} + +pub(crate) fn msg_sender>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, msg_sender(ptr)) +} + +pub(crate) fn msg_value>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, msg_value(ptr)) +} + +pub(crate) fn native_keccak256>( + mut env: WasmEnvMut, + input: GuestPtr, + len: u32, + output: GuestPtr, +) -> MaybeEscape { + hostio!(env, native_keccak256(input, len, output)) +} + +pub(crate) fn tx_gas_price>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, tx_gas_price(ptr)) +} + +pub(crate) fn tx_ink_price>( + mut env: WasmEnvMut, +) -> Result { + hostio!(env, tx_ink_price()) +} + +pub(crate) fn tx_origin>( + mut env: WasmEnvMut, + ptr: GuestPtr, +) -> MaybeEscape { + hostio!(env, tx_origin(ptr)) +} + +pub(crate) fn pay_for_memory_grow>( + mut env: WasmEnvMut, + pages: u16, +) -> MaybeEscape { + hostio!(env, pay_for_memory_grow(pages)) +} + +pub(crate) fn console_log_text>( + mut env: WasmEnvMut, + ptr: GuestPtr, + len: u32, +) -> MaybeEscape { + hostio!(env, console_log_text(ptr, len)) +} + +pub(crate) fn console_log, T: Into>( + mut env: WasmEnvMut, + value: T, +) -> MaybeEscape { + hostio!(env, console_log(value)) +} + +pub(crate) fn console_tee, T: Into + Copy>( + mut env: WasmEnvMut, + value: T, +) -> Result { + hostio!(env, console_tee(value)) +} + +pub(crate) fn null_host>(_: WasmEnvMut) {} diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs new file mode 100644 index 000000000..3c53359f8 --- /dev/null +++ b/arbitrator/stylus/src/lib.rs @@ -0,0 +1,276 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use arbutil::{ + evm::{ + api::DataReader, + req::EvmApiRequestor, + user::{UserOutcome, UserOutcomeKind}, + EvmData, + }, + format::DebugBytes, + Bytes32, +}; +use cache::InitCache; +use evm_api::NativeRequestHandler; +use eyre::ErrReport; +use native::NativeInstance; +use prover::programs::{prelude::*, StylusData}; +use run::RunProgram; +use std::{marker::PhantomData, mem, ptr}; + +pub use brotli; +pub use prover; + +pub mod env; +pub mod host; +pub mod native; +pub mod run; + +mod cache; +mod evm_api; +mod util; + +#[cfg(test)] +mod test; + +#[cfg(all(test, feature = "benchmark"))] +mod benchmarks; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct GoSliceData { + /// Points to data owned by Go. + ptr: *const u8, + /// The length in bytes. + len: usize, +} + +/// The data we're pointing to is owned by Go and has a lifetime no shorter than the current program. +unsafe impl Send for GoSliceData {} + +impl GoSliceData { + pub fn null() -> Self { + Self { + ptr: ptr::null(), + len: 0, + } + } + + fn slice(&self) -> &[u8] { + if self.len == 0 { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.ptr, self.len) } + } +} + +impl DataReader for GoSliceData { + fn slice(&self) -> &[u8] { + if self.len == 0 { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.ptr, self.len) } + } +} + +#[repr(C)] +pub struct RustSlice<'a> { + ptr: *const u8, + len: usize, + phantom: PhantomData<&'a [u8]>, +} + +impl<'a> RustSlice<'a> { + fn new(slice: &'a [u8]) -> Self { + Self { + ptr: slice.as_ptr(), + len: slice.len(), + phantom: PhantomData, + } + } +} + +#[repr(C)] +pub struct RustBytes { + ptr: *mut u8, + len: usize, + cap: usize, +} + +impl RustBytes { + unsafe fn into_vec(self) -> Vec { + Vec::from_raw_parts(self.ptr, self.len, self.cap) + } + + unsafe fn write(&mut self, mut vec: Vec) { + self.ptr = vec.as_mut_ptr(); + self.len = vec.len(); + self.cap = vec.capacity(); + mem::forget(vec); + } + + unsafe fn write_err(&mut self, err: ErrReport) -> UserOutcomeKind { + self.write(err.debug_bytes()); + UserOutcomeKind::Failure + } + + unsafe fn write_outcome(&mut self, outcome: UserOutcome) -> UserOutcomeKind { + let (status, outs) = outcome.into_data(); + self.write(outs); + status + } +} + +/// Instruments and "activates" a user wasm. +/// +/// The `output` is either the serialized asm & module pair or an error string. +/// Returns consensus info such as the module hash and footprint on success. +/// +/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. +/// The amount left is written back at the end of the call. +/// +/// # Safety +/// +/// `output`, `asm_len`, `module_hash`, `footprint`, and `gas` must not be null. +#[no_mangle] +pub unsafe extern "C" fn stylus_activate( + wasm: GoSliceData, + page_limit: u16, + version: u16, + debug: bool, + output: *mut RustBytes, + asm_len: *mut usize, + codehash: *const Bytes32, + module_hash: *mut Bytes32, + stylus_data: *mut StylusData, + gas: *mut u64, +) -> UserOutcomeKind { + let wasm = wasm.slice(); + let output = &mut *output; + let module_hash = &mut *module_hash; + let codehash = &*codehash; + let gas = &mut *gas; + + let (asm, module, info) = + match native::activate(wasm, codehash, version, page_limit, debug, gas) { + Ok(val) => val, + Err(err) => return output.write_err(err), + }; + *asm_len = asm.len(); + *module_hash = module.hash(); + *stylus_data = info; + + let mut data = asm; + data.extend(&*module.into_bytes()); + output.write(data); + UserOutcomeKind::Success +} + +/// Calls an activated user program. +/// +/// # Safety +/// +/// `module` must represent a valid module produced from `stylus_activate`. +/// `output` and `gas` must not be null. +#[no_mangle] +pub unsafe extern "C" fn stylus_call( + module: GoSliceData, + calldata: GoSliceData, + config: StylusConfig, + req_handler: NativeRequestHandler, + evm_data: EvmData, + debug_chain: bool, + output: *mut RustBytes, + gas: *mut u64, + long_term_tag: u32, +) -> UserOutcomeKind { + let module = module.slice(); + let calldata = calldata.slice().to_vec(); + let evm_api = EvmApiRequestor::new(req_handler); + let pricing = config.pricing; + let output = &mut *output; + let ink = pricing.gas_to_ink(*gas); + + // Safety: module came from compile_user_wasm and we've paid for memory expansion + let instance = unsafe { + NativeInstance::deserialize_cached( + module, + config.version, + evm_api, + evm_data, + long_term_tag, + debug_chain, + ) + }; + let mut instance = match instance { + Ok(instance) => instance, + Err(error) => util::panic_with_wasm(module, error.wrap_err("init failed")), + }; + + let status = match instance.run_main(&calldata, config, ink) { + Err(e) | Ok(UserOutcome::Failure(e)) => output.write_err(e.wrap_err("call failed")), + Ok(outcome) => output.write_outcome(outcome), + }; + let ink_left = match status { + UserOutcomeKind::OutOfStack => 0, // take all gas when out of stack + _ => instance.ink_left().into(), + }; + *gas = pricing.ink_to_gas(ink_left); + status +} + +/// resize lru +#[no_mangle] +pub extern "C" fn stylus_cache_lru_resize(size: u32) { + InitCache::set_lru_size(size); +} + +/// Caches an activated user program. +/// +/// # Safety +/// +/// `module` must represent a valid module produced from `stylus_activate`. +/// arbos_tag: a tag for arbos cache. 0 won't affect real caching +/// currently only if tag==1 caching will be affected +#[no_mangle] +pub unsafe extern "C" fn stylus_cache_module( + module: GoSliceData, + module_hash: Bytes32, + version: u16, + arbos_tag: u32, + debug: bool, +) { + if let Err(error) = InitCache::insert(module_hash, module.slice(), version, arbos_tag, debug) { + panic!("tried to cache invalid asm!: {error}"); + } +} + +/// Evicts an activated user program from the init cache. +#[no_mangle] +pub extern "C" fn stylus_evict_module( + module_hash: Bytes32, + version: u16, + arbos_tag: u32, + debug: bool, +) { + InitCache::evict(module_hash, version, arbos_tag, debug); +} + +/// Reorgs the init cache. This will likely never happen. +#[no_mangle] +pub extern "C" fn stylus_reorg_vm(_block: u64, arbos_tag: u32) { + InitCache::clear_long_term(arbos_tag); +} + +/// Frees the vector. Does nothing when the vector is null. +/// +/// # Safety +/// +/// Must only be called once per vec. +#[no_mangle] +pub unsafe extern "C" fn stylus_drop_vec(vec: RustBytes) { + if !vec.ptr.is_null() { + mem::drop(vec.into_vec()) + } +} diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs new file mode 100644 index 000000000..2858d59fd --- /dev/null +++ b/arbitrator/stylus/src/native.rs @@ -0,0 +1,454 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ + cache::InitCache, + env::{MeterData, WasmEnv}, + host, util, +}; +use arbutil::{ + evm::{ + api::{DataReader, EvmApi}, + EvmData, + }, + operator::OperatorCode, + Bytes32, Color, +}; +use eyre::{bail, eyre, ErrReport, Result}; +use prover::{ + machine::Module as ProverModule, + programs::{ + config::PricingParams, + counter::{Counter, CountingMachine, OP_OFFSETS}, + depth::STYLUS_STACK_LEFT, + meter::{STYLUS_INK_LEFT, STYLUS_INK_STATUS}, + prelude::*, + start::StartMover, + StylusData, + }, +}; +use std::{ + collections::BTreeMap, + fmt::Debug, + ops::{Deref, DerefMut}, +}; +use wasmer::{ + imports, AsStoreMut, Function, FunctionEnv, Instance, Memory, Module, Pages, Store, + TypedFunction, Value, WasmTypeList, +}; +use wasmer_vm::VMExtern; + +#[derive(Debug)] +pub struct NativeInstance> { + pub instance: Instance, + pub store: Store, + pub env: FunctionEnv>, +} + +impl> NativeInstance { + pub fn new(instance: Instance, store: Store, env: FunctionEnv>) -> Self { + let mut native = Self { + instance, + store, + env, + }; + if let Some(config) = native.env().config { + native.set_stack(config.max_depth); + } + native + } + + pub fn env(&self) -> &WasmEnv { + self.env.as_ref(&self.store) + } + + pub fn env_mut(&mut self) -> &mut WasmEnv { + self.env.as_mut(&mut self.store) + } + + pub fn config(&self) -> StylusConfig { + self.env().config.expect("no config") + } + + pub fn memory(&self) -> Memory { + self.env().memory.as_ref().unwrap().clone() + } + + pub fn memory_size(&self) -> Pages { + self.memory().ty(&self.store).minimum + } + + pub fn read_slice(&self, mem: &str, ptr: usize, len: usize) -> Result> { + let memory = self.exports.get_memory(mem)?; + let memory = memory.view(&self.store); + let mut data = vec![0; len]; + memory.read(ptr as u64, &mut data)?; + Ok(data) + } + + /// Creates a `NativeInstance` from a serialized module. + /// + /// # Safety + /// + /// `module` must represent a valid module. + pub unsafe fn deserialize( + module: &[u8], + compile: CompileConfig, + evm: E, + evm_data: EvmData, + ) -> Result { + let env = WasmEnv::new(compile, None, evm, evm_data); + let store = env.compile.store(); + let module = unsafe { Module::deserialize_unchecked(&store, module)? }; + Self::from_module(module, store, env) + } + + /// Creates a `NativeInstance` from a serialized module, or from a cached one if known. + /// + /// # Safety + /// + /// `module` must represent a valid module. + pub unsafe fn deserialize_cached( + module: &[u8], + version: u16, + evm: E, + evm_data: EvmData, + mut long_term_tag: u32, + debug: bool, + ) -> Result { + let compile = CompileConfig::version(version, debug); + let env = WasmEnv::new(compile, None, evm, evm_data); + let module_hash = env.evm_data.module_hash; + + if let Some((module, store)) = InitCache::get(module_hash, version, debug) { + return Self::from_module(module, store, env); + } + if !env.evm_data.cached { + long_term_tag = 0; + } + let (module, store) = + InitCache::insert(module_hash, module, version, long_term_tag, debug)?; + Self::from_module(module, store, env) + } + + pub fn from_path( + path: &str, + evm_api: E, + evm_data: EvmData, + compile: &CompileConfig, + config: StylusConfig, + ) -> Result { + let env = WasmEnv::new(compile.clone(), Some(config), evm_api, evm_data); + let store = env.compile.store(); + let wat_or_wasm = std::fs::read(path)?; + let module = Module::new(&store, wat_or_wasm)?; + Self::from_module(module, store, env) + } + + fn from_module(module: Module, mut store: Store, env: WasmEnv) -> Result { + let debug_funcs = env.compile.debug.debug_funcs; + let func_env = FunctionEnv::new(&mut store, env); + macro_rules! func { + ($func:expr) => { + Function::new_typed_with_env(&mut store, &func_env, $func) + }; + } + let mut imports = imports! { + "vm_hooks" => { + "read_args" => func!(host::read_args), + "write_result" => func!(host::write_result), + "exit_early" => func!(host::exit_early), + "storage_load_bytes32" => func!(host::storage_load_bytes32), + "storage_cache_bytes32" => func!(host::storage_cache_bytes32), + "storage_flush_cache" => func!(host::storage_flush_cache), + "transient_load_bytes32" => func!(host::transient_load_bytes32), + "transient_store_bytes32" => func!(host::transient_store_bytes32), + "call_contract" => func!(host::call_contract), + "delegate_call_contract" => func!(host::delegate_call_contract), + "static_call_contract" => func!(host::static_call_contract), + "create1" => func!(host::create1), + "create2" => func!(host::create2), + "read_return_data" => func!(host::read_return_data), + "return_data_size" => func!(host::return_data_size), + "emit_log" => func!(host::emit_log), + "account_balance" => func!(host::account_balance), + "account_code" => func!(host::account_code), + "account_codehash" => func!(host::account_codehash), + "account_code_size" => func!(host::account_code_size), + "evm_gas_left" => func!(host::evm_gas_left), + "evm_ink_left" => func!(host::evm_ink_left), + "block_basefee" => func!(host::block_basefee), + "chainid" => func!(host::chainid), + "block_coinbase" => func!(host::block_coinbase), + "block_gas_limit" => func!(host::block_gas_limit), + "block_number" => func!(host::block_number), + "block_timestamp" => func!(host::block_timestamp), + "contract_address" => func!(host::contract_address), + "math_div" => func!(host::math_div), + "math_mod" => func!(host::math_mod), + "math_pow" => func!(host::math_pow), + "math_add_mod" => func!(host::math_add_mod), + "math_mul_mod" => func!(host::math_mul_mod), + "msg_reentrant" => func!(host::msg_reentrant), + "msg_sender" => func!(host::msg_sender), + "msg_value" => func!(host::msg_value), + "tx_gas_price" => func!(host::tx_gas_price), + "tx_ink_price" => func!(host::tx_ink_price), + "tx_origin" => func!(host::tx_origin), + "pay_for_memory_grow" => func!(host::pay_for_memory_grow), + "native_keccak256" => func!(host::native_keccak256), + }, + }; + if debug_funcs { + imports.define("console", "log_txt", func!(host::console_log_text)); + imports.define("console", "log_i32", func!(host::console_log::)); + imports.define("console", "log_i64", func!(host::console_log::)); + imports.define("console", "log_f32", func!(host::console_log::)); + imports.define("console", "log_f64", func!(host::console_log::)); + imports.define("console", "tee_i32", func!(host::console_tee::)); + imports.define("console", "tee_i64", func!(host::console_tee::)); + imports.define("console", "tee_f32", func!(host::console_tee::)); + imports.define("console", "tee_f64", func!(host::console_tee::)); + imports.define("debug", "null_host", func!(host::null_host)); + } + let instance = Instance::new(&mut store, &module, &imports)?; + let exports = &instance.exports; + let memory = exports.get_memory("memory")?.clone(); + + let env = func_env.as_mut(&mut store); + env.memory = Some(memory); + + let mut native = Self::new(instance, store, func_env); + native.set_meter_data(); + Ok(native) + } + + pub fn set_meter_data(&mut self) { + let store = &mut self.store; + let exports = &self.instance.exports; + + let mut expect_global = |name| { + let VMExtern::Global(sh) = exports.get_extern(name).unwrap().to_vm_extern() else { + panic!("name not found global"); + }; + sh.get(store.objects_mut()).vmglobal() + }; + let ink_left = expect_global(STYLUS_INK_LEFT); + let ink_status = expect_global(STYLUS_INK_STATUS); + + self.env_mut().meter = Some(MeterData { + ink_left, + ink_status, + }); + } + + pub fn get_global(&mut self, name: &str) -> Result + where + T: TryFrom, + T::Error: Debug, + { + let store = &mut self.store.as_store_mut(); + let Ok(global) = self.instance.exports.get_global(name) else { + bail!("global {} does not exist", name.red()) + }; + let ty = global.get(store); + + ty.try_into() + .map_err(|_| eyre!("global {} has the wrong type", name.red())) + } + + pub fn set_global(&mut self, name: &str, value: T) -> Result<()> + where + T: Into, + { + let store = &mut self.store.as_store_mut(); + let Ok(global) = self.instance.exports.get_global(name) else { + bail!("global {} does not exist", name.red()) + }; + global.set(store, value.into()).map_err(ErrReport::msg) + } + + pub fn call_func(&mut self, func: TypedFunction<(), R>, ink: u64) -> Result + where + R: WasmTypeList, + { + self.set_ink(ink); + Ok(func.call(&mut self.store)?) + } +} + +impl> Deref for NativeInstance { + type Target = Instance; + + fn deref(&self) -> &Self::Target { + &self.instance + } +} + +impl> DerefMut for NativeInstance { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.instance + } +} + +impl> MeteredMachine for NativeInstance { + fn ink_left(&self) -> MachineMeter { + let vm = self.env().meter(); + match vm.status() { + 0 => MachineMeter::Ready(vm.ink()), + _ => MachineMeter::Exhausted, + } + } + + fn set_meter(&mut self, meter: MachineMeter) { + let vm = self.env_mut().meter_mut(); + vm.set_ink(meter.ink()); + vm.set_status(meter.status()); + } +} + +impl> GasMeteredMachine for NativeInstance { + fn pricing(&self) -> PricingParams { + self.env().config.unwrap().pricing + } +} + +impl> CountingMachine for NativeInstance { + fn operator_counts(&mut self) -> Result> { + let mut counts = BTreeMap::new(); + + for (&op, &offset) in OP_OFFSETS.lock().iter() { + let count: u64 = self.get_global(&Counter::global_name(offset))?; + if count != 0 { + counts.insert(op, count); + } + } + Ok(counts) + } +} + +impl> DepthCheckedMachine for NativeInstance { + fn stack_left(&mut self) -> u32 { + self.get_global(STYLUS_STACK_LEFT).unwrap() + } + + fn set_stack(&mut self, size: u32) { + self.set_global(STYLUS_STACK_LEFT, size).unwrap() + } +} + +impl> StartlessMachine for NativeInstance { + fn get_start(&self) -> Result> { + let store = &self.store; + let exports = &self.instance.exports; + exports + .get_typed_function(store, StartMover::NAME) + .map_err(ErrReport::new) + } +} + +pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> { + let mut store = compile.store(); + let module = Module::new(&store, wasm)?; + macro_rules! stub { + (u8 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> u8 { panic!("incomplete import") }) + }; + (u32 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> u32 { panic!("incomplete import") }) + }; + (u64 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> u64 { panic!("incomplete import") }) + }; + (f32 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> f32 { panic!("incomplete import") }) + }; + (f64 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> f64 { panic!("incomplete import") }) + }; + ($($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ panic!("incomplete import")) + }; + } + let mut imports = imports! { + "vm_hooks" => { + "read_args" => stub!(|_: u32|), + "write_result" => stub!(|_: u32, _: u32|), + "exit_early" => stub!(|_: u32|), + "storage_load_bytes32" => stub!(|_: u32, _: u32|), + "storage_cache_bytes32" => stub!(|_: u32, _: u32|), + "storage_flush_cache" => stub!(|_: u32|), + "transient_load_bytes32" => stub!(|_: u32, _: u32|), + "transient_store_bytes32" => stub!(|_: u32, _: u32|), + "call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u32, _: u64, _: u32|), + "delegate_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|), + "static_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|), + "create1" => stub!(|_: u32, _: u32, _: u32, _: u32, _: u32|), + "create2" => stub!(|_: u32, _: u32, _: u32, _: u32, _: u32, _: u32|), + "read_return_data" => stub!(u32 <- |_: u32, _: u32, _: u32|), + "return_data_size" => stub!(u32 <- ||), + "emit_log" => stub!(|_: u32, _: u32, _: u32|), + "account_balance" => stub!(|_: u32, _: u32|), + "account_code" => stub!(u32 <- |_: u32, _: u32, _: u32, _: u32|), + "account_codehash" => stub!(|_: u32, _: u32|), + "account_code_size" => stub!(u32 <- |_: u32|), + "evm_gas_left" => stub!(u64 <- ||), + "evm_ink_left" => stub!(u64 <- ||), + "block_basefee" => stub!(|_: u32|), + "chainid" => stub!(u64 <- ||), + "block_coinbase" => stub!(|_: u32|), + "block_gas_limit" => stub!(u64 <- ||), + "block_number" => stub!(u64 <- ||), + "block_timestamp" => stub!(u64 <- ||), + "contract_address" => stub!(|_: u32|), + "math_div" => stub!(|_: u32, _: u32|), + "math_mod" => stub!(|_: u32, _: u32|), + "math_pow" => stub!(|_: u32, _: u32|), + "math_add_mod" => stub!(|_: u32, _: u32, _: u32|), + "math_mul_mod" => stub!(|_: u32, _: u32, _: u32|), + "msg_reentrant" => stub!(u32 <- ||), + "msg_sender" => stub!(|_: u32|), + "msg_value" => stub!(|_: u32|), + "tx_gas_price" => stub!(|_: u32|), + "tx_ink_price" => stub!(u32 <- ||), + "tx_origin" => stub!(|_: u32|), + "pay_for_memory_grow" => stub!(|_: u16|), + "native_keccak256" => stub!(|_: u32, _: u32, _: u32|), + }, + }; + if compile.debug.debug_funcs { + imports.define("console", "log_txt", stub!(|_: u32, _: u32|)); + imports.define("console", "log_i32", stub!(|_: u32|)); + imports.define("console", "log_i64", stub!(|_: u64|)); + imports.define("console", "log_f32", stub!(|_: f32|)); + imports.define("console", "log_f64", stub!(|_: f64|)); + imports.define("console", "tee_i32", stub!(u32 <- |_: u32|)); + imports.define("console", "tee_i64", stub!(u64 <- |_: u64|)); + imports.define("console", "tee_f32", stub!(f32 <- |_: f32|)); + imports.define("console", "tee_f64", stub!(f64 <- |_: f64|)); + imports.define("debug", "null_host", stub!(||)); + } + Instance::new(&mut store, &module, &imports)?; + + let module = module.serialize()?; + Ok(module.to_vec()) +} + +pub fn activate( + wasm: &[u8], + codehash: &Bytes32, + version: u16, + page_limit: u16, + debug: bool, + gas: &mut u64, +) -> Result<(Vec, ProverModule, StylusData)> { + let compile = CompileConfig::version(version, debug); + let (module, stylus_data) = + ProverModule::activate(wasm, codehash, version, page_limit, debug, gas)?; + + let asm = match self::module(wasm, compile) { + Ok(asm) => asm, + Err(err) => util::panic_with_wasm(wasm, err), + }; + Ok((asm, module, stylus_data)) +} diff --git a/arbitrator/stylus/src/run.rs b/arbitrator/stylus/src/run.rs new file mode 100644 index 000000000..8e673a25e --- /dev/null +++ b/arbitrator/stylus/src/run.rs @@ -0,0 +1,123 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::redundant_closure_call)] + +use crate::{env::Escape, native::NativeInstance}; +use arbutil::evm::api::{DataReader, EvmApi}; +use arbutil::evm::user::UserOutcome; +use eyre::{eyre, Result}; +use prover::machine::Machine; +use prover::programs::{prelude::*, STYLUS_ENTRY_POINT}; + +pub trait RunProgram { + fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result; +} + +impl RunProgram for Machine { + fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result { + macro_rules! call { + ($module:expr, $func:expr, $args:expr) => { + call!($module, $func, $args, |error| UserOutcome::Failure(error)) + }; + ($module:expr, $func:expr, $args:expr, $error:expr) => {{ + match self.call_function($module, $func, $args) { + Ok(value) => value[0].try_into().unwrap(), + Err(error) => return Ok($error(error)), + } + }}; + } + + // push the args + let args_len = (args.len() as u32).into(); + let push_vec = vec![ + args_len, + config.version.into(), + config.max_depth.into(), + config.pricing.ink_price.into(), + ]; + let args_ptr = call!("user_test", "prepare", push_vec); + let user_host = self.find_module("user_test")?; + self.write_memory(user_host, args_ptr, args)?; + + self.set_ink(ink); + self.set_stack(config.max_depth); + + let status: u32 = call!("user", STYLUS_ENTRY_POINT, vec![args_len], |error| { + if self.stack_left() == 0 { + return UserOutcome::OutOfStack; + } + if self.ink_left() == MachineMeter::Exhausted { + return UserOutcome::OutOfInk; + } + UserOutcome::Failure(error) + }); + + let outs_ptr = call!("user_test", "get_outs_ptr", vec![]); + let outs_len = call!("user_test", "get_outs_len", vec![]); + let outs = self.read_memory(user_host, outs_ptr, outs_len)?.to_vec(); + + Ok(match status { + 0 => UserOutcome::Success(outs), + _ => UserOutcome::Revert(outs), + }) + } +} + +impl> RunProgram for NativeInstance { + fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result { + use UserOutcome::*; + + self.set_ink(ink); + self.set_stack(config.max_depth); + + let store = &mut self.store; + let env = self.env.as_mut(store); + env.args = args.to_owned(); + env.outs.clear(); + env.config = Some(config); + + if env.evm_data.tracing { + let args_len = args.len() as u32; + env.evm_api + .capture_hostio(STYLUS_ENTRY_POINT, &args_len.to_be_bytes(), &[], ink, ink); + } + + let exports = &self.instance.exports; + let main = exports.get_typed_function::(store, STYLUS_ENTRY_POINT)?; + let status = match main.call(store, args.len() as u32) { + Ok(status) => status, + Err(outcome) => { + if self.stack_left() == 0 { + return Ok(OutOfStack); + } + if self.ink_left() == MachineMeter::Exhausted { + return Ok(OutOfInk); + } + + let escape: Escape = match outcome.downcast() { + Ok(escape) => escape, + Err(error) => return Ok(Failure(eyre!(error).wrap_err("hard user error"))), + }; + match escape { + Escape::OutOfInk => return Ok(OutOfInk), + Escape::Memory(error) => return Ok(Failure(error.into())), + Escape::Internal(error) | Escape::Logical(error) => return Ok(Failure(error)), + Escape::Exit(status) => status, + } + } + }; + + let env = self.env_mut(); + if env.evm_data.tracing { + env.evm_api + .capture_hostio("user_returned", &[], &status.to_be_bytes(), ink, ink); + } + + let outs = env.outs.clone(); + Ok(match status { + 0 => UserOutcome::Success(outs), + _ => UserOutcome::Revert(outs), + }) + } +} diff --git a/arbitrator/stylus/src/test/api.rs b/arbitrator/stylus/src/test/api.rs new file mode 100644 index 000000000..92d731791 --- /dev/null +++ b/arbitrator/stylus/src/test/api.rs @@ -0,0 +1,210 @@ +// Copyright 2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{native, run::RunProgram}; +use arbutil::{ + evm::{ + api::{EvmApi, VecReader}, + user::UserOutcomeKind, + EvmData, + }, + Bytes20, Bytes32, +}; +use eyre::Result; +use parking_lot::Mutex; +use prover::programs::{memory::MemoryModel, prelude::*}; +use std::{collections::HashMap, sync::Arc}; + +use super::TestInstance; + +#[derive(Clone, Debug)] +pub(crate) struct TestEvmApi { + contracts: Arc>>>, + storage: Arc>>>, + program: Bytes20, + write_result: Arc>>, + compile: CompileConfig, + configs: Arc>>, + evm_data: EvmData, + pages: Arc>, +} + +impl TestEvmApi { + pub fn new(compile: CompileConfig) -> (TestEvmApi, EvmData) { + let program = Bytes20::default(); + let evm_data = EvmData::default(); + + let mut storage = HashMap::new(); + storage.insert(program, HashMap::new()); + + let api = TestEvmApi { + contracts: Arc::new(Mutex::new(HashMap::new())), + storage: Arc::new(Mutex::new(storage)), + program, + write_result: Arc::new(Mutex::new(vec![])), + compile, + configs: Arc::new(Mutex::new(HashMap::new())), + evm_data, + pages: Arc::new(Mutex::new((0, 0))), + }; + (api, evm_data) + } + + pub fn deploy(&mut self, address: Bytes20, config: StylusConfig, name: &str) -> Result<()> { + let file = format!("tests/{name}/target/wasm32-unknown-unknown/release/{name}.wasm"); + let wasm = std::fs::read(file)?; + let module = native::module(&wasm, self.compile.clone())?; + self.contracts.lock().insert(address, module); + self.configs.lock().insert(address, config); + Ok(()) + } + + pub fn set_pages(&mut self, open: u16) { + let mut pages = self.pages.lock(); + pages.0 = open; + pages.1 = open.max(pages.1); + } +} + +impl EvmApi for TestEvmApi { + fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + let storage = &mut self.storage.lock(); + let storage = storage.get_mut(&self.program).unwrap(); + let value = storage.get(&key).cloned().unwrap_or_default(); + (value, 2100) // pretend worst case + } + + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + let storage = &mut self.storage.lock(); + let storage = storage.get_mut(&self.program).unwrap(); + storage.insert(key, value); + 0 + } + + fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result { + let storage = &mut self.storage.lock(); + let storage = storage.get_mut(&self.program).unwrap(); + Ok(22100 * storage.len() as u64) // pretend worst case + } + + fn get_transient_bytes32(&mut self, _key: Bytes32) -> Bytes32 { + unimplemented!("tload not supported") + } + + fn set_transient_bytes32(&mut self, _key: Bytes32, _value: Bytes32) -> Result<()> { + unimplemented!("tstore not supported") + } + + /// Simulates a contract call. + /// Note: this call function is for testing purposes only and deviates from onchain behavior. + fn contract_call( + &mut self, + contract: Bytes20, + calldata: &[u8], + _gas_left: u64, + gas_req: u64, + _value: Bytes32, + ) -> (u32, u64, UserOutcomeKind) { + let compile = self.compile.clone(); + let evm_data = self.evm_data; + let config = *self.configs.lock().get(&contract).unwrap(); + let gas = gas_req; // Not consensus behavior + + let mut native = unsafe { + let contracts = self.contracts.lock(); + let module = contracts.get(&contract).unwrap(); + TestInstance::deserialize(module, compile, self.clone(), evm_data).unwrap() + }; + + let ink = config.pricing.gas_to_ink(gas); + let outcome = native.run_main(calldata, config, ink).unwrap(); + let (status, outs) = outcome.into_data(); + let outs_len = outs.len() as u32; + + let ink_left: u64 = native.ink_left().into(); + let gas_left = config.pricing.ink_to_gas(ink_left); + *self.write_result.lock() = outs; + (outs_len, gas - gas_left, status) + } + + fn delegate_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas_left: u64, + _gas_req: u64, + ) -> (u32, u64, UserOutcomeKind) { + todo!("delegate call not yet supported") + } + + fn static_call( + &mut self, + contract: Bytes20, + calldata: &[u8], + gas_left: u64, + gas_req: u64, + ) -> (u32, u64, UserOutcomeKind) { + println!("note: overriding static call with call"); + self.contract_call(contract, calldata, gas_left, gas_req, Bytes32::default()) + } + + fn create1( + &mut self, + _code: Vec, + _endowment: Bytes32, + _gas: u64, + ) -> (Result, u32, u64) { + unimplemented!("create1 not supported") + } + + fn create2( + &mut self, + _code: Vec, + _endowment: Bytes32, + _salt: Bytes32, + _gas: u64, + ) -> (Result, u32, u64) { + unimplemented!("create2 not supported") + } + + fn get_return_data(&self) -> VecReader { + VecReader::new(self.write_result.lock().clone()) + } + + fn emit_log(&mut self, _data: Vec, _topics: u32) -> Result<()> { + Ok(()) // pretend a log was emitted + } + + fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, u64) { + unimplemented!() + } + + fn account_code(&mut self, _address: Bytes20, _gas_left: u64) -> (VecReader, u64) { + unimplemented!() + } + + fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) { + unimplemented!() + } + + fn add_pages(&mut self, new: u16) -> u64 { + let model = MemoryModel::new(2, 1000); + let (open, ever) = *self.pages.lock(); + + let mut pages = self.pages.lock(); + pages.0 = pages.0.saturating_add(new); + pages.1 = pages.1.max(pages.0); + model.gas_cost(new, open, ever) + } + + fn capture_hostio( + &mut self, + _name: &str, + _args: &[u8], + _outs: &[u8], + _start_ink: u64, + _end_ink: u64, + ) { + unimplemented!() + } +} diff --git a/arbitrator/stylus/src/test/misc.rs b/arbitrator/stylus/src/test/misc.rs new file mode 100644 index 000000000..ae44a885f --- /dev/null +++ b/arbitrator/stylus/src/test/misc.rs @@ -0,0 +1,82 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use super::test_configs; +use crate::{ + env::{Escape, MaybeEscape}, + native::NativeInstance, + test::{check_instrumentation, new_test_machine}, +}; +use eyre::Result; +use prover::programs::{prelude::*, start::StartMover}; +use wasmer::{imports, Function}; + +#[test] +fn test_bulk_memory() -> Result<()> { + let (compile, config, ink) = test_configs(); + let mut store = compile.store(); + let filename = "../prover/test-cases/bulk-memory.wat"; + let imports = imports! { + "env" => { + "wavm_halt_and_set_finished" => Function::new_typed(&mut store, || -> MaybeEscape { Escape::logical("done") }), + }, + }; + + let mut native = NativeInstance::new_from_store(filename, store, imports)?; + native.set_meter_data(); + + let starter = native.get_start()?; + native.set_stack(config.max_depth); + native.set_ink(ink); + starter.call(&mut native.store).unwrap_err(); + assert_ne!(native.ink_left(), MachineMeter::Exhausted); + + let expected = "0000080808050205000002020500020508000000000000000000000000000000"; + let data = native.read_slice("memory", 0x1000, 32)?; + assert_eq!(expected, hex::encode(data)); + + let mut machine = new_test_machine(filename, &compile)?; + let module = machine.find_module("user")?; + drop(machine.call_user_func("start", vec![], ink).unwrap_err()); // should halt + let data = machine.read_memory(module, 0x1000, 32)?; + assert_eq!(expected, hex::encode(data)); + + check_instrumentation(native, machine) +} + +#[test] +fn test_bulk_memory_oob() -> Result<()> { + let filename = "tests/bulk-memory-oob.wat"; + let (compile, _, ink) = test_configs(); + + let mut machine = new_test_machine(filename, &compile)?; + let mut native = NativeInstance::new_test(filename, compile)?; + let module = machine.find_module("user")?; + + let oobs = ["fill", "copy_left", "copy_right", "copy_same"]; + for oob in &oobs { + drop(machine.call_user_func(oob, vec![], ink).unwrap_err()); + + let exports = &native.instance.exports; + let oob = exports.get_typed_function::<(), ()>(&native.store, oob)?; + let err = format!("{}", native.call_func(oob, ink).unwrap_err()); + assert!(err.contains("out of bounds memory access")); + } + assert_eq!("0102", hex::encode(native.read_slice("memory", 0xfffe, 2)?)); + assert_eq!("0102", hex::encode(machine.read_memory(module, 0xfffe, 2)?)); + check_instrumentation(native, machine) +} + +#[test] +fn test_console() -> Result<()> { + let filename = "tests/console.wat"; + let (compile, config, ink) = test_configs(); + + let mut native = NativeInstance::new_linked(filename, &compile, config)?; + let starter = native.get_start()?; + native.call_func(starter, ink)?; + + let mut machine = new_test_machine(filename, &compile)?; + machine.call_user_func(StartMover::NAME, vec![], ink)?; + check_instrumentation(native, machine) +} diff --git a/arbitrator/stylus/src/test/mod.rs b/arbitrator/stylus/src/test/mod.rs new file mode 100644 index 000000000..d7f3248d3 --- /dev/null +++ b/arbitrator/stylus/src/test/mod.rs @@ -0,0 +1,196 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{env::WasmEnv, native::NativeInstance, run::RunProgram, test::api::TestEvmApi}; +use arbutil::{ + evm::{api::VecReader, user::UserOutcome}, + Bytes20, Bytes32, Color, +}; +use eyre::{bail, Result}; +use prover::{ + machine::GlobalState, + programs::{config::SigMap, prelude::*}, + Machine, +}; +use rand::prelude::*; +use std::{collections::HashMap, path::Path, sync::Arc}; +use wasmer::{ + imports, wasmparser::Operator, CompilerConfig, Function, FunctionEnv, Imports, Instance, + Module, Store, +}; +use wasmer_compiler_singlepass::Singlepass; + +mod api; +mod misc; +mod native; +mod sdk; +mod wavm; + +#[cfg(feature = "timings")] +mod timings; + +type TestInstance = NativeInstance; + +impl TestInstance { + fn new_test(path: &str, compile: CompileConfig) -> Result { + let mut store = compile.store(); + let imports = imports! { + "test" => { + "noop" => Function::new_typed(&mut store, || {}), + }, + }; + let mut native = Self::new_from_store(path, store, imports)?; + native.set_meter_data(); + native.set_ink(u64::MAX); + native.set_stack(u32::MAX); + Ok(native) + } + + fn new_from_store(path: &str, mut store: Store, imports: Imports) -> Result { + let wat = std::fs::read(path)?; + let module = Module::new(&store, wat)?; + let native = Instance::new(&mut store, &module, &imports)?; + Ok(Self::new_sans_env(native, store)) + } + + fn new_vanilla(path: &str) -> Result { + let mut compiler = Singlepass::new(); + compiler.canonicalize_nans(true); + compiler.enable_verifier(); + + let mut store = Store::new(compiler); + let wat = std::fs::read(path)?; + let module = Module::new(&store, wat)?; + let instance = Instance::new(&mut store, &module, &Imports::new())?; + Ok(Self::new_sans_env(instance, store)) + } + + fn new_sans_env(instance: Instance, mut store: Store) -> Self { + let compile = CompileConfig::default(); + let (evm, evm_data) = TestEvmApi::new(compile.clone()); + let env = FunctionEnv::new(&mut store, WasmEnv::new(compile, None, evm, evm_data)); + Self::new(instance, store, env) + } + + fn new_linked( + path: impl AsRef, + compile: &CompileConfig, + config: StylusConfig, + ) -> Result { + Self::new_with_evm(path.as_ref(), compile, config).map(|x| x.0) + } + + fn new_with_evm( + path: &str, + compile: &CompileConfig, + config: StylusConfig, + ) -> Result<(Self, TestEvmApi)> { + let (mut evm, evm_data) = TestEvmApi::new(compile.clone()); + let native = Self::from_path(path, evm.clone(), evm_data, compile, config)?; + let footprint = native.memory().ty(&native.store).minimum.0 as u16; + evm.set_pages(footprint); + Ok((native, evm)) + } +} + +fn expensive_add(op: &Operator, _tys: &SigMap) -> u64 { + match op { + Operator::I32Add => 100, + _ => 0, + } +} + +pub fn random_ink(min: u64) -> u64 { + rand::thread_rng().gen_range(min..=u64::MAX) +} + +pub fn random_bytes20() -> Bytes20 { + let mut data = [0; 20]; + rand::thread_rng().fill_bytes(&mut data); + data.into() +} + +fn random_bytes32() -> Bytes32 { + let mut data = [0; 32]; + rand::thread_rng().fill_bytes(&mut data); + data.into() +} + +fn test_compile_config() -> CompileConfig { + let mut compile_config = CompileConfig::version(0, true); + compile_config.debug.count_ops = true; + compile_config +} + +fn uniform_cost_config() -> StylusConfig { + let mut stylus_config = StylusConfig::default(); + stylus_config.pricing.ink_price = 10000; + stylus_config +} + +fn test_configs() -> (CompileConfig, StylusConfig, u64) { + ( + test_compile_config(), + uniform_cost_config(), + random_ink(1_000_000), + ) +} + +fn new_test_machine(path: &str, compile: &CompileConfig) -> Result { + let wat = std::fs::read(path)?; + let wasm = wasmer::wat2wasm(&wat)?; + let mut bin = prover::binary::parse(&wasm, Path::new("user"))?; + let stylus_data = bin.instrument(compile, &Bytes32::default())?; + + let wat = std::fs::read("tests/test.wat")?; + let wasm = wasmer::wat2wasm(&wat)?; + let lib = prover::binary::parse(&wasm, Path::new("test"))?; + + let mut mach = Machine::from_binaries( + &[lib], + bin, + false, + false, + true, + compile.debug.debug_funcs, + true, + GlobalState::default(), + HashMap::default(), + Arc::new(|_, _, _| panic!("tried to read preimage")), + Some(stylus_data), + )?; + mach.set_ink(u64::MAX); + mach.set_stack(u32::MAX); + Ok(mach) +} + +fn run_native(native: &mut TestInstance, args: &[u8], ink: u64) -> Result> { + let config = native.env().config.expect("no config"); + match native.run_main(args, config, ink)? { + UserOutcome::Success(output) => Ok(output), + err => bail!("user program failure: {}", err.red()), + } +} + +fn run_machine( + machine: &mut Machine, + args: &[u8], + config: StylusConfig, + ink: u64, +) -> Result> { + match machine.run_main(args, config, ink)? { + UserOutcome::Success(output) => Ok(output), + err => bail!("user program failure: {}", err.red()), + } +} + +fn check_instrumentation(mut native: TestInstance, mut machine: Machine) -> Result<()> { + assert_eq!(native.ink_left(), machine.ink_left()); + assert_eq!(native.stack_left(), machine.stack_left()); + + let native_counts = native.operator_counts()?; + let machine_counts = machine.operator_counts()?; + assert_eq!(native_counts.get(&Operator::Unreachable.into()), None); + assert_eq!(native_counts, machine_counts); + Ok(()) +} diff --git a/arbitrator/stylus/src/test/native.rs b/arbitrator/stylus/src/test/native.rs new file mode 100644 index 000000000..503e5875f --- /dev/null +++ b/arbitrator/stylus/src/test/native.rs @@ -0,0 +1,499 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow( + clippy::field_reassign_with_default, + clippy::inconsistent_digit_grouping +)] + +use crate::{ + run::RunProgram, + test::{ + check_instrumentation, random_bytes20, random_bytes32, random_ink, run_machine, run_native, + test_compile_config, test_configs, TestInstance, + }, +}; +use arbutil::{ + crypto, + evm::{ + api::EvmApi, + user::{UserOutcome, UserOutcomeKind}, + }, + format, Bytes20, Bytes32, Color, +}; +use eyre::{bail, ensure, Result}; +use prover::{ + binary, + programs::{ + counter::{Counter, CountingMachine}, + prelude::*, + start::StartMover, + MiddlewareWrapper, ModuleMod, + }, + Machine, +}; +use std::{collections::HashMap, path::Path, sync::Arc, time::Instant}; +use wasmer::wasmparser::Operator; +use wasmer::{CompilerConfig, ExportIndex, Imports, Pages, Store}; +use wasmer_compiler_singlepass::Singlepass; + +#[test] +fn test_ink() -> Result<()> { + let mut compile = test_compile_config(); + compile.pricing.costs = super::expensive_add; + + let mut native = TestInstance::new_test("tests/add.wat", compile)?; + let exports = &native.exports; + let add_one = exports.get_typed_function::(&native.store, "add_one")?; + + macro_rules! exhaust { + ($ink:expr) => { + native.set_ink($ink); + assert_eq!(native.ink_left(), MachineMeter::Ready($ink)); + assert!(add_one.call(&mut native.store, 32).is_err()); + assert_eq!(native.ink_left(), MachineMeter::Exhausted); + }; + } + + exhaust!(0); + exhaust!(50); + exhaust!(99); + + let mut ink_left = 500; + native.set_ink(ink_left); + while ink_left > 0 { + assert_eq!(native.ink_left(), MachineMeter::Ready(ink_left)); + assert_eq!(add_one.call(&mut native.store, 64)?, 65); + ink_left -= 100; + } + assert!(add_one.call(&mut native.store, 32).is_err()); + assert_eq!(native.ink_left(), MachineMeter::Exhausted); + Ok(()) +} + +#[test] +fn test_depth() -> Result<()> { + // in depth.wat + // the `depth` global equals the number of times `recurse` is called + // the `recurse` function calls itself + // the `recurse` function has 1 parameter and 2 locals + // comments show that the max depth is 3 words + + let mut native = TestInstance::new_test("tests/depth.wat", test_compile_config())?; + let exports = &native.exports; + let recurse = exports.get_typed_function::(&native.store, "recurse")?; + + let program_depth: u32 = native.get_global("depth")?; + assert_eq!(program_depth, 0); + + let mut check = |space: u32, expected: u32| -> Result<()> { + native.set_global("depth", 0)?; + native.set_stack(space); + assert_eq!(native.stack_left(), space); + + assert!(recurse.call(&mut native.store, 0).is_err()); + assert_eq!(native.stack_left(), 0); + + let program_depth: u32 = native.get_global("depth")?; + assert_eq!(program_depth, expected); + Ok(()) + }; + + let locals = 2; + let depth = 3; + let fixed = 4; + + let frame_size = locals + depth + fixed; + + check(frame_size, 0)?; // should immediately exhaust (space left <= frame) + check(frame_size + 1, 1)?; + check(2 * frame_size, 1)?; + check(2 * frame_size + 1, 2)?; + check(4 * frame_size, 3)?; + check(4 * frame_size + frame_size / 2, 4) +} + +#[test] +fn test_start() -> Result<()> { + // in start.wat + // the `status` global equals 10 at initialization + // the `start` function increments `status` + // by the spec, `start` must run at initialization + + fn check(native: &mut TestInstance, value: i32) -> Result<()> { + let status: i32 = native.get_global("status")?; + assert_eq!(status, value); + Ok(()) + } + + let mut native = TestInstance::new_vanilla("tests/start.wat")?; + check(&mut native, 11)?; + + let mut native = TestInstance::new_test("tests/start.wat", test_compile_config())?; + check(&mut native, 10)?; + + let exports = &native.exports; + let move_me = exports.get_typed_function::<(), ()>(&native.store, "move_me")?; + let starter = native.get_start()?; + let ink = random_ink(100_000); + + native.call_func(move_me, ink)?; + native.call_func(starter, ink)?; + check(&mut native, 12)?; + Ok(()) +} + +#[test] +fn test_count() -> Result<()> { + let mut compiler = Singlepass::new(); + compiler.canonicalize_nans(true); + compiler.enable_verifier(); + + let starter = StartMover::new(true); + let counter = Counter::new(); + compiler.push_middleware(Arc::new(MiddlewareWrapper::new(starter))); + compiler.push_middleware(Arc::new(MiddlewareWrapper::new(counter))); + + let mut instance = + TestInstance::new_from_store("tests/clz.wat", Store::new(compiler), Imports::new())?; + + let starter = instance.get_start()?; + starter.call(&mut instance.store)?; + + let counts = instance.operator_counts()?; + let check = |value: Operator<'_>| counts.get(&value.into()); + + use Operator::*; + assert_eq!(check(Unreachable), None); + assert_eq!(check(Drop), Some(&1)); + assert_eq!(check(I64Clz), Some(&1)); + + // test the instrumentation's contribution + assert_eq!(check(GlobalGet { global_index: 0 }), Some(&8)); // one in clz.wat + assert_eq!(check(GlobalSet { global_index: 0 }), Some(&7)); + assert_eq!(check(I64Add), Some(&7)); + assert_eq!(check(I64Const { value: 0 }), Some(&7)); + Ok(()) +} + +#[test] +fn test_import_export_safety() -> Result<()> { + // test wasms + // bad-export.wat there's a global named `stylus_ink_left` + // bad-export2.wat there's a func named `stylus_global_with_random_name` + // bad-import.wat there's an import named `stylus_global_with_random_name` + + fn check(file: &str, both: bool, instrument: bool) -> Result<()> { + let path = &Path::new(file); + let wat = std::fs::read(path)?; + let wasm = wasmer::wat2wasm(&wat)?; + let bin = binary::parse(&wasm, path); + if !instrument { + assert!(bin.is_err()); + return Ok(()); + } + + let codehash = &Bytes32::default(); + let mut compile = test_compile_config(); + let mut bin = bin?; + assert!(bin.clone().instrument(&compile, codehash).is_err()); + compile.debug.debug_info = false; + assert!(bin.instrument(&compile, &codehash).is_err()); + + if both { + assert!(TestInstance::new_test(file, compile).is_err()); + } + Ok(()) + } + + // TODO: perform all the same checks in instances + check("tests/bad-mods/bad-export.wat", true, false)?; + check("tests/bad-mods/bad-export2.wat", true, false)?; + check("tests/bad-mods/bad-export3.wat", true, true)?; + check("tests/bad-mods/bad-export4.wat", false, true)?; + check("tests/bad-mods/bad-import.wat", true, false) +} + +#[test] +fn test_module_mod() -> Result<()> { + // in module-mod.wat + // the func `void` has the signature λ() + // the func `more` has the signature λ(i32, i64) -> f32 + // the func `noop` is imported + + let file = "tests/module-mod.wat"; + let wat = std::fs::read(file)?; + let wasm = wasmer::wat2wasm(&wat)?; + let binary = binary::parse(&wasm, Path::new(file))?; + + let native = TestInstance::new_test(file, test_compile_config())?; + let module = native.module().info(); + + assert_eq!(module.all_functions()?, binary.all_functions()?); + assert_eq!(module.all_signatures()?, binary.all_signatures()?); + + let check = |name: &str| { + let Some(ExportIndex::Function(func)) = module.exports.get(name) else { + bail!("no func named {}", name.red()) + }; + let wasmer_ty = module.get_function(*func)?; + let binary_ty = binary.get_function(*func)?; + assert_eq!(wasmer_ty, binary_ty); + println!("{} {}", func.as_u32(), binary_ty.blue()); + Ok(()) + }; + + check("void")?; + check("more") +} + +#[test] +fn test_heap() -> Result<()> { + // in memory.wat + // the input is the target size and amount to step each `memory.grow` + // the output is the memory size in pages + + let (mut compile, config, _) = test_configs(); + compile.bounds.heap_bound = Pages(128); + compile.pricing.costs = |_, _| 0; + + let extra: u8 = rand::random::() % 128; + + for step in 1..128 { + let (mut native, _) = TestInstance::new_with_evm("tests/memory.wat", &compile, config)?; + let ink = random_ink(32_000_000); + let args = vec![128, step]; + + let pages = run_native(&mut native, &args, ink)?[0]; + assert_eq!(pages, 128); + + let used = config.pricing.ink_to_gas(ink - native.ink_ready()?); + ensure!((used as i64 - 32_000_000).abs() < 3_000, "wrong ink"); + assert_eq!(native.memory_size(), Pages(128)); + + if step == extra { + let mut machine = Machine::from_user_path(Path::new("tests/memory.wat"), &compile)?; + run_machine(&mut machine, &args, config, ink)?; + check_instrumentation(native, machine)?; + } + } + + // in memory2.wat + // the user program calls pay_for_memory_grow directly with malicious arguments + // the cost should exceed a maximum u32, consuming more gas than can ever be bought + + let (mut native, _) = TestInstance::new_with_evm("tests/memory2.wat", &compile, config)?; + let outcome = native.run_main(&[], config, config.pricing.ink_to_gas(u32::MAX.into()))?; + assert_eq!(outcome.kind(), UserOutcomeKind::OutOfInk); + + // ensure we reject programs with excessive footprints + compile.bounds.heap_bound = Pages(0); + _ = TestInstance::new_with_evm("tests/memory.wat", &compile, config).unwrap_err(); + _ = Machine::from_user_path(Path::new("tests/memory.wat"), &compile).unwrap_err(); + Ok(()) +} + +#[test] +fn test_rust() -> Result<()> { + // in keccak.rs + // the input is the # of hashings followed by a preimage + // the output is the iterated hash of the preimage + + let filename = "tests/keccak/target/wasm32-unknown-unknown/release/keccak.wasm"; + let preimage = "°º¤ø,¸,ø¤°º¤ø,¸,ø¤°º¤ø,¸ nyan nyan ~=[,,_,,]:3 nyan nyan"; + let preimage = preimage.as_bytes().to_vec(); + let hash = hex::encode(crypto::keccak(&preimage)); + let (compile, config, ink) = test_configs(); + + let mut args = vec![0x01]; + args.extend(preimage); + + let mut native = TestInstance::new_linked(filename, &compile, config)?; + let start = Instant::now(); + let output = run_native(&mut native, &args, ink)?; + println!("Exec {}", format::time(start.elapsed())); + assert_eq!(hex::encode(output), hash); + + let mut machine = Machine::from_user_path(Path::new(filename), &compile)?; + let start = Instant::now(); + let output = run_machine(&mut machine, &args, config, ink)?; + assert_eq!(hex::encode(output), hash); + println!("Exec {}", format::time(start.elapsed())); + + check_instrumentation(native, machine) +} + +#[test] +fn test_fallible() -> Result<()> { + // in fallible.rs + // an input starting with 0x00 will execute an unreachable + // an empty input induces a panic + + let filename = "tests/fallible/target/wasm32-unknown-unknown/release/fallible.wasm"; + let (compile, config, ink) = test_configs(); + + let mut native = TestInstance::new_linked(filename, &compile, config)?; + match native.run_main(&[0x00], config, ink)? { + UserOutcome::Failure(err) => println!("{}", format!("{err:?}").grey()), + err => bail!("expected hard error: {}", err.red()), + } + match native.run_main(&[], config, ink)? { + UserOutcome::Failure(err) => println!("{}", format!("{err:?}").grey()), + err => bail!("expected hard error: {}", err.red()), + } + + let mut machine = Machine::from_user_path(Path::new(filename), &compile)?; + match machine.run_main(&[0x00], config, ink)? { + UserOutcome::Failure(err) => println!("{}", format!("{err:?}").grey()), + err => bail!("expected hard error: {}", err.red()), + } + match machine.run_main(&[], config, ink)? { + UserOutcome::Failure(err) => println!("{}", format!("{err:?}").grey()), + err => bail!("expected hard error: {}", err.red()), + } + + let native_counts = native.operator_counts()?; + let machine_counts = machine.operator_counts()?; + assert_eq!(native_counts, machine_counts); + assert_eq!(native.ink_left(), machine.ink_left()); + assert_eq!(native.stack_left(), machine.stack_left()); + Ok(()) +} + +#[test] +fn test_storage() -> Result<()> { + // in storage.rs + // an input starting with 0x00 will induce a storage read + // all other inputs induce a storage write + + let filename = "tests/storage/target/wasm32-unknown-unknown/release/storage.wasm"; + let (compile, config, ink) = test_configs(); + + let key = crypto::keccak(filename.as_bytes()); + let value = crypto::keccak("value".as_bytes()); + + let mut store_args = vec![0x01]; + store_args.extend(key); + store_args.extend(value); + + let mut load_args = vec![0x00]; + load_args.extend(key); + + let (mut native, mut evm) = TestInstance::new_with_evm(filename, &compile, config)?; + run_native(&mut native, &store_args, ink)?; + assert_eq!(evm.get_bytes32(key.into()).0, Bytes32(value)); + assert_eq!(run_native(&mut native, &load_args, ink)?, value); + + let mut machine = Machine::from_user_path(Path::new(filename), &compile)?; + run_machine(&mut machine, &store_args, config, ink)?; + assert_eq!(run_machine(&mut machine, &load_args, config, ink)?, value); + + check_instrumentation(native, machine) +} + +#[test] +fn test_calls() -> Result<()> { + // in call.rs + // the first bytes determines the number of calls to make + // each call starts with a length specifying how many input bytes it constitutes + // the first byte determines the kind of call to be made (normal, delegate, or static) + // the next 20 bytes select the address you want to call, with the rest being calldata + // + // in storage.rs + // an input starting with 0x00 will induce a storage read + // all other inputs induce a storage write + + let calls_addr = random_bytes20(); + let store_addr = random_bytes20(); + println!("calls.wasm {}", calls_addr); + println!("store.wasm {}", store_addr); + + let mut slots = HashMap::new(); + + /// Forms a 2ary call tree where each leaf writes a random storage cell. + fn nest( + level: usize, + calls: Bytes20, + store: Bytes20, + slots: &mut HashMap, + ) -> Vec { + let mut args = vec![]; + + if level == 0 { + // call storage.wasm + args.push(0x00); + args.extend(Bytes32::default()); + args.extend(store); + + let key = random_bytes32(); + let value = random_bytes32(); + slots.insert(key, value); + + // insert value @ key + args.push(0x01); + args.extend(key); + args.extend(value); + return args; + } + + // do the two following calls + args.push(0x00); + args.extend(Bytes32::default()); + args.extend(calls); + args.push(2); + + for _ in 0..2 { + let inner = nest(level - 1, calls, store, slots); + args.extend(u32::to_be_bytes(inner.len() as u32)); + args.extend(inner); + } + args + } + + // drop the first address to start the call tree + let tree = nest(3, calls_addr, store_addr, &mut slots); + let args = tree[53..].to_vec(); + println!("ARGS {}", hex::encode(&args)); + + let filename = "tests/multicall/target/wasm32-unknown-unknown/release/multicall.wasm"; + let (compile, config, ink) = test_configs(); + + let (mut native, mut evm) = TestInstance::new_with_evm(filename, &compile, config)?; + evm.deploy(calls_addr, config, "multicall")?; + evm.deploy(store_addr, config, "storage")?; + + run_native(&mut native, &args, ink)?; + + for (key, value) in slots { + assert_eq!(evm.get_bytes32(key).0, value); + } + Ok(()) +} + +#[test] +fn test_exit_early() -> Result<()> { + // in exit-early.wat + // the input is returned as the output + // the status code is the first byte + // + // in panic-after-write.wat + // the program writes a result but then panics + + let file = |f: &str| format!("tests/exit-early/{f}.wat"); + let (compile, config, ink) = test_configs(); + let args = &[0x01; 32]; + + let mut native = TestInstance::new_linked(file("exit-early"), &compile, config)?; + let output = match native.run_main(args, config, ink)? { + UserOutcome::Revert(output) => output, + err => bail!("expected revert: {}", err.red()), + }; + assert_eq!(hex::encode(output), hex::encode(args)); + + let mut native = TestInstance::new_linked(file("panic-after-write"), &compile, config)?; + match native.run_main(args, config, ink)? { + UserOutcome::Failure(error) => println!("{error:?}"), + err => bail!("expected hard error: {}", err.red()), + } + Ok(()) +} diff --git a/arbitrator/stylus/src/test/sdk.rs b/arbitrator/stylus/src/test/sdk.rs new file mode 100644 index 000000000..1f5f4b1c7 --- /dev/null +++ b/arbitrator/stylus/src/test/sdk.rs @@ -0,0 +1,65 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::field_reassign_with_default)] + +use crate::test::{random_bytes20, random_bytes32, run_native, test_configs, TestInstance}; +use eyre::Result; +use num_bigint::BigUint; + +#[test] +fn test_sdk_routes() -> Result<()> { + let filename = "tests/erc20/target/wasm32-unknown-unknown/release/erc20.wasm"; + + macro_rules! hex { + ($($hex:expr),+) => { + hex::decode(&format!($($hex),+))? + }; + } + + let (compile, config, ink) = test_configs(); + let (mut native, mut evm) = TestInstance::new_with_evm(filename, &compile, config)?; + + // deploy a copy to another address + let imath = random_bytes20(); + evm.deploy(imath, config, "erc20")?; + + // call balanceOf(0x000..000) + let calldata = hex!("70a082310000000000000000000000000000000000000000000000000000000000000000"); + let output = run_native(&mut native, &calldata, ink)?; + assert_eq!(output, [0; 32]); + + // call mint() + let calldata = hex!("1249c58b"); + let output = run_native(&mut native, &calldata, ink)?; + assert!(output.is_empty()); + + macro_rules! big { + ($int:expr) => { + &format!("{:0>64}", $int.to_str_radix(16)) + }; + } + + // sumWithHelper(imath, values) + let imath = BigUint::from_bytes_be(&imath.0); + let count = 10_u8; + let method = "168261a9"; // sumWithHelper + let mut calldata = hex!( + "{method}{}{}{}", + big!(imath), + big!(BigUint::from(64_u8)), + big!(BigUint::from(count)) + ); + + let mut sum = BigUint::default(); + for _ in 0..count { + let value = BigUint::from_bytes_be(&random_bytes32().0); + calldata.extend(hex!("{}", big!(value))); + sum += value; + } + sum %= BigUint::from(2_u8).pow(256); + + let output = run_native(&mut native, &calldata, ink)?; + assert_eq!(&hex::encode(output), big!(sum)); + Ok(()) +} diff --git a/arbitrator/stylus/src/test/timings.rs b/arbitrator/stylus/src/test/timings.rs new file mode 100644 index 000000000..5419dd5dd --- /dev/null +++ b/arbitrator/stylus/src/test/timings.rs @@ -0,0 +1,73 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::test::{run_native, test_configs, TestInstance}; +use arbutil::{color::Color, format}; +use eyre::Result; +use std::time::Instant; + +#[test] +fn test_timings() -> Result<()> { + let (mut compile, config, ink) = test_configs(); + compile.debug.count_ops = false; + + #[rustfmt::skip] + let basic = [ + // simple returns + "null_host", "return_data_size", "block_gas_limit", "block_timestamp", "tx_ink_price", + + // gas left + "evm_gas_left", "evm_ink_left", + + // evm data + "block_basefee", "chainid", "block_coinbase", "block_number", "contract_address", + "msg_sender", "msg_value", "tx_gas_price", "tx_origin", + ]; + + let loops = ["read_args", "write_result", "keccak"]; + + macro_rules! run { + ($rounds:expr, $args:expr, $file:expr) => {{ + let mut native = TestInstance::new_linked(&$file, &compile, config)?; + let before = Instant::now(); + run_native(&mut native, &$args, ink)?; + let time = before.elapsed() / $rounds; + let cost = time.as_nanos() as f64 / 10.39; // 10.39 from Rachel's desktop + let ink = format!("{}", (cost * 10000.).ceil() as usize).grey(); + (format::time(time), format!("{cost:.4}").grey(), ink) + }}; + } + + macro_rules! args { + ($rounds:expr, $len:expr) => {{ + let mut args = $rounds.to_le_bytes().to_vec(); + args.extend(vec![1; $len - 4]); + args + }}; + } + + println!("Timings hostios. Please note the values derived are machine dependent.\n"); + + println!("\n{}", format!("Hostio timings").pink()); + for name in basic { + let file = format!("tests/timings/{name}.wat"); + let rounds: u32 = 50_000_000; + let (time, cost, ink) = run!(rounds, rounds.to_le_bytes(), file); + println!("{} {time} {cost} {ink}", format!("{name:16}").grey()); + } + + for name in loops { + println!("\n{}", format!("{name} timings").pink()); + for i in 2..10 { + let file = format!("tests/timings/{name}.wat"); + let rounds: u32 = 10_000_000; + let size = 1 << i; + let args = args!(rounds, size); + + let (time, cost, ink) = run!(rounds, args, file); + let name = format!("{name}({size:03})").grey(); + println!("{name} {time} {cost} {ink}",); + } + } + Ok(()) +} diff --git a/arbitrator/stylus/src/test/wavm.rs b/arbitrator/stylus/src/test/wavm.rs new file mode 100644 index 000000000..e707cf490 --- /dev/null +++ b/arbitrator/stylus/src/test/wavm.rs @@ -0,0 +1,104 @@ +// Copyright 2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::test::{new_test_machine, test_compile_config}; +use eyre::Result; +use prover::{programs::prelude::*, Machine}; + +#[test] +fn test_ink() -> Result<()> { + let mut compile = test_compile_config(); + compile.pricing.costs = super::expensive_add; + + let machine = &mut new_test_machine("tests/add.wat", &compile)?; + let call = |mech: &mut Machine, v: u32| mech.call_function("user", "add_one", vec![v.into()]); + + macro_rules! exhaust { + ($ink:expr) => { + machine.set_ink($ink); + assert_eq!(machine.ink_left(), MachineMeter::Ready($ink)); + assert!(call(machine, 32).is_err()); + assert_eq!(machine.ink_left(), MachineMeter::Exhausted); + }; + } + + exhaust!(0); + exhaust!(50); + exhaust!(99); + + let mut ink_left = 500; + machine.set_ink(ink_left); + while ink_left > 0 { + assert_eq!(machine.ink_left(), MachineMeter::Ready(ink_left)); + assert_eq!(call(machine, 64)?, vec![65_u32.into()]); + ink_left -= 100; + } + assert!(call(machine, 32).is_err()); + assert_eq!(machine.ink_left(), MachineMeter::Exhausted); + Ok(()) +} + +#[test] +fn test_depth() -> Result<()> { + // in depth.wat + // the `depth` global equals the number of times `recurse` is called + // the `recurse` function calls itself + // the `recurse` function has 1 parameter and 2 locals + // comments show that the max depth is 3 words + + let machine = &mut new_test_machine("tests/depth.wat", &test_compile_config())?; + let call = |mech: &mut Machine| mech.call_function("user", "recurse", vec![0_u64.into()]); + + let program_depth: u32 = machine.get_global("depth")?.try_into()?; + assert_eq!(program_depth, 0); + + let mut check = |space: u32, expected: u32| -> Result<()> { + machine.set_global("depth", 0_u32.into())?; + machine.set_stack(space); + assert_eq!(machine.stack_left(), space); + + assert!(call(machine).is_err()); + assert_eq!(machine.stack_left(), 0); + + let program_depth: u32 = machine.get_global("depth")?.try_into()?; + assert_eq!(program_depth, expected); + Ok(()) + }; + + let locals = 2; + let depth = 3; + let fixed = 4; + + let frame_size = locals + depth + fixed; + + check(frame_size, 0)?; // should immediately exhaust (space left <= frame) + check(frame_size + 1, 1)?; + check(2 * frame_size, 1)?; + check(2 * frame_size + 1, 2)?; + check(4 * frame_size, 3)?; + check(4 * frame_size + frame_size / 2, 4) +} + +#[test] +fn test_start() -> Result<()> { + // in start.wat + // the `status` global equals 10 at initialization + // the `start` function increments `status` + // by the spec, `start` must run at initialization + + fn check(machine: &mut Machine, value: u32) -> Result<()> { + let status: u32 = machine.get_global("status")?.try_into()?; + assert_eq!(status, value); + Ok(()) + } + + let compile = test_compile_config(); + let machine = &mut new_test_machine("tests/start.wat", &compile)?; + check(machine, 10)?; + + let call = |mech: &mut Machine, name: &str| mech.call_function("user", name, vec![]); + + call(machine, "move_me")?; + call(machine, "stylus_start")?; + check(machine, 12) +} diff --git a/arbitrator/stylus/src/util.rs b/arbitrator/stylus/src/util.rs new file mode 100644 index 000000000..71a7baf9b --- /dev/null +++ b/arbitrator/stylus/src/util.rs @@ -0,0 +1,20 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use arbutil::crypto; +use eyre::Report; + +/// This function panics while saving an offending wasm to disk. +pub fn panic_with_wasm(wasm: &[u8], error: Report) -> ! { + // save at a deterministic path + let hash = hex::encode(crypto::keccak(wasm)); + let mut path = std::env::temp_dir(); + path.push(format!("stylus-panic-{hash}.wasm")); + + // try to save to disk, otherwise dump to the console + if let Err(io_error) = std::fs::write(&path, wasm) { + let wasm = hex::encode(wasm); + panic!("failed to write fatal wasm {error:?}: {io_error:?}\nwasm: {wasm}"); + } + panic!("encountered fatal wasm: {error:?}"); +} diff --git a/arbitrator/stylus/tests/.cargo/config.toml b/arbitrator/stylus/tests/.cargo/config.toml new file mode 100644 index 000000000..ff01b6685 --- /dev/null +++ b/arbitrator/stylus/tests/.cargo/config.toml @@ -0,0 +1,9 @@ +[build] +target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-arg=-zstack-size=8192", +# "-C", "link-arg=--export=__heap_base", +# "-C", "link-arg=--export=__data_end", +] diff --git a/arbitrator/stylus/tests/add.wat b/arbitrator/stylus/tests/add.wat new file mode 100644 index 000000000..70aa71078 --- /dev/null +++ b/arbitrator/stylus/tests/add.wat @@ -0,0 +1,14 @@ +;; Copyright 2022, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (memory 0 0) + (export "memory" (memory 0)) + (type $t0 (func (param i32) (result i32))) + (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32) + get_local $p0 + i32.const 1 + i32.add) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) + )) diff --git a/arbitrator/stylus/tests/bad-mods/bad-export.wat b/arbitrator/stylus/tests/bad-mods/bad-export.wat new file mode 100644 index 000000000..80c029166 --- /dev/null +++ b/arbitrator/stylus/tests/bad-mods/bad-export.wat @@ -0,0 +1,5 @@ +;; Copyright 2022-2024, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (global $status (export "stylus_ink_left") (mut i64) (i64.const -1))) diff --git a/arbitrator/stylus/tests/bad-mods/bad-export2.wat b/arbitrator/stylus/tests/bad-mods/bad-export2.wat new file mode 100644 index 000000000..907cc299c --- /dev/null +++ b/arbitrator/stylus/tests/bad-mods/bad-export2.wat @@ -0,0 +1,5 @@ +;; Copyright 2022-2024, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (func (export "stylus_global_with_random_name"))) diff --git a/arbitrator/stylus/tests/bad-mods/bad-export3.wat b/arbitrator/stylus/tests/bad-mods/bad-export3.wat new file mode 100644 index 000000000..30232916f --- /dev/null +++ b/arbitrator/stylus/tests/bad-mods/bad-export3.wat @@ -0,0 +1,5 @@ +;; Copyright 2024, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (func (export "memory"))) diff --git a/arbitrator/stylus/tests/bad-mods/bad-export4.wat b/arbitrator/stylus/tests/bad-mods/bad-export4.wat new file mode 100644 index 000000000..47142990a --- /dev/null +++ b/arbitrator/stylus/tests/bad-mods/bad-export4.wat @@ -0,0 +1,7 @@ +;; Copyright 2024, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (global (export "user_entrypoint") i32 (i32.const 0)) + (memory (export "memory") 0 0) +) diff --git a/arbitrator/stylus/tests/bad-mods/bad-import.wat b/arbitrator/stylus/tests/bad-mods/bad-import.wat new file mode 100644 index 000000000..ec2a951fb --- /dev/null +++ b/arbitrator/stylus/tests/bad-mods/bad-import.wat @@ -0,0 +1,5 @@ +;; Copyright 2022-2024, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (import "env" "stylus_global_with_random_name" (func))) diff --git a/arbitrator/stylus/tests/bf/.gitignore b/arbitrator/stylus/tests/bf/.gitignore new file mode 100644 index 000000000..373b847ef --- /dev/null +++ b/arbitrator/stylus/tests/bf/.gitignore @@ -0,0 +1,2 @@ +**.wat +**.wasm diff --git a/arbitrator/stylus/tests/bf/cat.b b/arbitrator/stylus/tests/bf/cat.b new file mode 100644 index 000000000..05b49e264 --- /dev/null +++ b/arbitrator/stylus/tests/bf/cat.b @@ -0,0 +1 @@ +,[.,] diff --git a/arbitrator/stylus/tests/bulk-memory-oob.wat b/arbitrator/stylus/tests/bulk-memory-oob.wat new file mode 100644 index 000000000..dceba8ec2 --- /dev/null +++ b/arbitrator/stylus/tests/bulk-memory-oob.wat @@ -0,0 +1,17 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (func (export "fill") + (memory.fill (i32.const 0xffff) (i32.const 0) (i32.const 2))) + (func (export "copy_left") + (memory.copy (i32.const 0xffff) (i32.const 0xfffe) (i32.const 2))) + (func (export "copy_right") + (memory.copy (i32.const 0xfffe) (i32.const 0xffff) (i32.const 2))) + (func (export "copy_same") + (memory.copy (i32.const 0xffff) (i32.const 0xffff) (i32.const 2))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) + ) + (data (i32.const 0xfffe) "\01\02") ;; last two bytes shouldn't change + (memory (export "memory") 1 1)) diff --git a/arbitrator/stylus/tests/clz.wat b/arbitrator/stylus/tests/clz.wat new file mode 100644 index 000000000..4504d7887 --- /dev/null +++ b/arbitrator/stylus/tests/clz.wat @@ -0,0 +1,11 @@ +;; Copyright 2022-2023, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (global $global (mut i64) (i64.const 32)) + (func $start + global.get $global + i64.clz + drop + ) + (start $start)) diff --git a/arbitrator/stylus/tests/console.wat b/arbitrator/stylus/tests/console.wat new file mode 100644 index 000000000..28162cf2c --- /dev/null +++ b/arbitrator/stylus/tests/console.wat @@ -0,0 +1,37 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "console" "log_txt" (func $log_txt (param i32 i32))) + (import "console" "log_i32" (func $log_i32 (param i32))) + (import "console" "log_i64" (func $log_i64 (param i64))) + (import "console" "log_f32" (func $log_f32 (param f32))) + (import "console" "log_f64" (func $log_f64 (param f64))) + (import "console" "tee_i32" (func $tee_i32 (param i32) (result i32))) + (import "console" "tee_i64" (func $tee_i64 (param i64) (result i64))) + (import "console" "tee_f32" (func $tee_f32 (param f32) (result f32))) + (import "console" "tee_f64" (func $tee_f64 (param f64) (result f64))) + (memory (export "memory") 1 1) + (data (i32.const 0xa4b) "\57\65\20\68\61\76\65\20\74\68\65\20\69\6E\6B\21") ;; We have the ink! + (func $start + (call $log_txt (i32.const 0xa4b) (i32.const 16)) + + i32.const 48 + call $tee_i32 + call $log_i32 + + i64.const 96 + call $tee_i64 + call $log_i64 + + f32.const 0.32 + call $tee_f32 + call $log_f32 + + f64.const -64.32 + call $tee_f64 + call $log_f64) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) + ) + (start $start)) diff --git a/arbitrator/stylus/tests/create/.cargo/config b/arbitrator/stylus/tests/create/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/create/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/create/Cargo.lock b/arbitrator/stylus/tests/create/Cargo.lock new file mode 100644 index 000000000..ca6be1f23 --- /dev/null +++ b/arbitrator/stylus/tests/create/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "create" +version = "0.1.0" +dependencies = [ + "hex", + "stylus-sdk", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/create/Cargo.toml b/arbitrator/stylus/tests/create/Cargo.toml new file mode 100644 index 000000000..b800033dd --- /dev/null +++ b/arbitrator/stylus/tests/create/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "create" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } +hex = "0.4.3" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/create/src/main.rs b/arbitrator/stylus/tests/create/src/main.rs new file mode 100644 index 000000000..d5ef19af6 --- /dev/null +++ b/arbitrator/stylus/tests/create/src/main.rs @@ -0,0 +1,26 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{alloy_primitives::{B256, U256}, deploy::RawDeploy, evm, prelude::*}; + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let kind = input[0]; + let mut input = &input[1..]; + + let endowment = U256::from_be_bytes::<32>(input[..32].try_into().unwrap()); + input = &input[32..]; + + let mut salt = None; + if kind == 2 { + salt = Some(B256::try_from(&input[..32]).unwrap()); + input = &input[32..]; + } + + let code = input; + let contract = unsafe { RawDeploy::new().salt_option(salt).deploy(code, endowment)? }; + evm::raw_log(&[contract.into_word()], &[]).unwrap(); + Ok(contract.to_vec()) +} diff --git a/arbitrator/stylus/tests/depth.wat b/arbitrator/stylus/tests/depth.wat new file mode 100644 index 000000000..21ca44066 --- /dev/null +++ b/arbitrator/stylus/tests/depth.wat @@ -0,0 +1,18 @@ +;; Copyright 2022, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (import "test" "noop" (func)) + (memory 0 0) + (export "memory" (memory 0)) + (global $depth (export "depth") (mut i32) (i32.const 0)) + (func $recurse (export "recurse") (param $ignored i64) (local f32 f64) + local.get $ignored ;; push 1 -- 1 on stack + global.get $depth ;; push 1 -- 2 on stack + i32.const 1 ;; push 1 -- 3 on stack <- 3 words max + i32.add ;; pop 2, push 1 -- 2 on stack + global.set $depth ;; pop 1 -- 1 on stack + call $recurse) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) + )) diff --git a/arbitrator/stylus/tests/erc20/Cargo.lock b/arbitrator/stylus/tests/erc20/Cargo.lock new file mode 100644 index 000000000..c3e215978 --- /dev/null +++ b/arbitrator/stylus/tests/erc20/Cargo.lock @@ -0,0 +1,890 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f73f11dcfbf8bb763d88fb1d082fe7cca0a00d3227d9921bdbd52ce5e013e2" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "proptest", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f938f00332d63a5b0ac687bd6f46d03884638948921d9f8b50c59563d421ae25" +dependencies = [ + "arrayvec", + "bytes", + "smol_str", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "erc20" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "hex", + "stylus-sdk", + "wee_alloc", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "regex", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/erc20/Cargo.toml b/arbitrator/stylus/tests/erc20/Cargo.toml new file mode 100644 index 000000000..859bfe50a --- /dev/null +++ b/arbitrator/stylus/tests/erc20/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "erc20" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy-primitives = "0.3.1" +alloy-sol-types = "0.3.1" +stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["reentrant"] } +hex = "0.4.3" +wee_alloc = "0.4.5" + +[features] +export-abi = ["stylus-sdk/export-abi"] + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" +opt-level = "s" + +[workspace] diff --git a/arbitrator/stylus/tests/erc20/src/erc20.rs b/arbitrator/stylus/tests/erc20/src/erc20.rs new file mode 100644 index 000000000..da41fe297 --- /dev/null +++ b/arbitrator/stylus/tests/erc20/src/erc20.rs @@ -0,0 +1,178 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +// Warning: this code is for testing only and has not been audited + +use alloc::{string::String, vec::Vec}; +use core::marker::PhantomData; +use stylus_sdk::{ + alloy_primitives::{Address, U256}, + alloy_sol_types::{sol, SolError}, + evm, msg, + prelude::*, +}; + +pub trait Erc20Params { + const NAME: &'static str; + const SYMBOL: &'static str; + const DECIMALS: u8; +} + +sol_storage! { + /// Erc20 implements all ERC-20 methods. + pub struct Erc20 { + /// Maps users to balances + mapping(address => uint256) balances; + /// Maps users to a mapping of each spender's allowance + mapping(address => mapping(address => uint256)) allowances; + /// The total supply of the token + uint256 total_supply; + /// Used to allow [`Erc20Params`] + PhantomData phantom; + } +} + +// Declare events and Solidity error types +sol! { + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + error InsufficientBalance(address from, uint256 have, uint256 want); + error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want); +} + +pub enum Erc20Error { + InsufficientBalance(InsufficientBalance), + InsufficientAllowance(InsufficientAllowance), +} + +// We will soon provide a #[derive(SolidityError)] to clean this up +impl From for Vec { + fn from(err: Erc20Error) -> Vec { + match err { + Erc20Error::InsufficientBalance(e) => e.encode(), + Erc20Error::InsufficientAllowance(e) => e.encode(), + } + } +} + +// These methods aren't exposed to other contracts +// Note: modifying storage will become much prettier soon +impl Erc20 { + pub fn transfer_impl( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result<(), Erc20Error> { + let mut sender_balance = self.balances.setter(from); + let old_sender_balance = sender_balance.get(); + if old_sender_balance < value { + return Err(Erc20Error::InsufficientBalance(InsufficientBalance { + from, + have: old_sender_balance, + want: value, + })); + } + sender_balance.set(old_sender_balance - value); + let mut to_balance = self.balances.setter(to); + let new_to_balance = to_balance.get() + value; + to_balance.set(new_to_balance); + evm::log(Transfer { from, to, value }); + Ok(()) + } + + pub fn mint(&mut self, address: Address, value: U256) { + let mut balance = self.balances.setter(address); + let new_balance = balance.get() + value; + balance.set(new_balance); + self.total_supply.set(self.total_supply.get() + value); + evm::log(Transfer { + from: Address::ZERO, + to: address, + value, + }); + } + + pub fn burn(&mut self, address: Address, value: U256) -> Result<(), Erc20Error> { + let mut balance = self.balances.setter(address); + let old_balance = balance.get(); + if old_balance < value { + return Err(Erc20Error::InsufficientBalance(InsufficientBalance { + from: address, + have: old_balance, + want: value, + })); + } + balance.set(old_balance - value); + self.total_supply.set(self.total_supply.get() - value); + evm::log(Transfer { + from: address, + to: Address::ZERO, + value, + }); + Ok(()) + } +} + +// These methods are external to other contracts +// Note: modifying storage will become much prettier soon +#[external] +impl Erc20 { + pub fn name() -> Result { + Ok(T::NAME.into()) + } + + pub fn symbol() -> Result { + Ok(T::SYMBOL.into()) + } + + pub fn decimals() -> Result { + Ok(T::DECIMALS) + } + + pub fn balance_of(&self, address: Address) -> Result { + Ok(self.balances.get(address)) + } + + pub fn transfer(&mut self, to: Address, value: U256) -> Result { + self.transfer_impl(msg::sender(), to, value)?; + Ok(true) + } + + pub fn approve(&mut self, spender: Address, value: U256) -> Result { + self.allowances.setter(msg::sender()).insert(spender, value); + evm::log(Approval { + owner: msg::sender(), + spender, + value, + }); + Ok(true) + } + + pub fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + let mut sender_allowances = self.allowances.setter(from); + let mut allowance = sender_allowances.setter(msg::sender()); + let old_allowance = allowance.get(); + if old_allowance < value { + return Err(Erc20Error::InsufficientAllowance(InsufficientAllowance { + owner: from, + spender: msg::sender(), + have: old_allowance, + want: value, + })); + } + allowance.set(old_allowance - value); + self.transfer_impl(from, to, value)?; + Ok(true) + } + + pub fn allowance(&self, owner: Address, spender: Address) -> Result { + Ok(self.allowances.getter(owner).get(spender)) + } +} diff --git a/arbitrator/stylus/tests/erc20/src/main.rs b/arbitrator/stylus/tests/erc20/src/main.rs new file mode 100644 index 000000000..7cbda7ef3 --- /dev/null +++ b/arbitrator/stylus/tests/erc20/src/main.rs @@ -0,0 +1,70 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +// Warning: this code is for testing only and has not been audited + +#![cfg_attr(not(feature = "export-abi"), no_main, no_std)] +extern crate alloc; + +use crate::erc20::{Erc20, Erc20Params}; +use alloc::{string::String, vec::Vec}; +use stylus_sdk::{alloy_primitives::U256, call, msg, prelude::*}; + +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +mod erc20; + +struct WethParams; + +/// Immutable definitions +impl Erc20Params for WethParams { + const NAME: &'static str = "Wrapped Ether Example"; + const SYMBOL: &'static str = "WETH"; + const DECIMALS: u8 = 18; +} + +// The contract +sol_storage! { + #[entrypoint] // Makes Weth the entrypoint + struct Weth { + #[borrow] // Allows erc20 to access Weth's storage and make calls + Erc20 erc20; + } +} + +// Another contract we'd like to call +sol_interface! { + interface IMath { + function sumValues(uint256[] values) pure returns (string, uint256); + } +} + +#[external] +#[inherit(Erc20)] +impl Weth { + #[payable] + pub fn mint(&mut self) -> Result<(), Vec> { + self.erc20.mint(msg::sender(), msg::value()); + Ok(()) + } + + pub fn burn(&mut self, amount: U256) -> Result<(), Vec> { + self.erc20.burn(msg::sender(), amount)?; + + // send the user their funds + call::transfer_eth(self, msg::sender(), amount) + } + + // sums numbers + pub fn sum_values(values: Vec) -> Result<(String, U256), Vec> { + Ok(("sum".into(), values.iter().sum())) + } + + // calls the sum_values() method from the interface + pub fn sum_with_helper(&self, helper: IMath, values: Vec) -> Result> { + let (text, sum) = helper.sum_values(self, values)?; + assert_eq!(&text, "sum"); + Ok(sum) + } +} diff --git a/arbitrator/stylus/tests/evm-data/.cargo/config b/arbitrator/stylus/tests/evm-data/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/evm-data/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/evm-data/Cargo.lock b/arbitrator/stylus/tests/evm-data/Cargo.lock new file mode 100644 index 000000000..c78abc9f1 --- /dev/null +++ b/arbitrator/stylus/tests/evm-data/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "evm-data" +version = "0.1.0" +dependencies = [ + "hex", + "stylus-sdk", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/evm-data/Cargo.toml b/arbitrator/stylus/tests/evm-data/Cargo.toml new file mode 100644 index 000000000..3549af52e --- /dev/null +++ b/arbitrator/stylus/tests/evm-data/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "evm-data" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["debug"] } +hex = "0.4.3" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/evm-data/src/main.rs b/arbitrator/stylus/tests/evm-data/src/main.rs new file mode 100644 index 000000000..850d51176 --- /dev/null +++ b/arbitrator/stylus/tests/evm-data/src/main.rs @@ -0,0 +1,85 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +extern crate alloc; + +use stylus_sdk::{ + alloy_primitives::{Address, B256, U256}, + block, + call::RawCall, + contract, evm, msg, + prelude::*, + tx, +}; + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let balance_check_addr = Address::try_from(&input[..20]).unwrap(); + let eth_precompile_addr = Address::try_from(&input[20..40]).unwrap(); + let arb_test_addr = Address::try_from(&input[40..60]).unwrap(); + let contract_addr = Address::try_from(&input[60..80]).unwrap(); + let burn_call_data = &input[80..]; + + let address_balance = balance_check_addr.balance(); + let eth_precompile_codehash = eth_precompile_addr.codehash(); + let arb_precompile_codehash = arb_test_addr.codehash(); + let contract_codehash = contract_addr.codehash(); + + let code = contract_addr.code(); + assert_eq!(code.len(), contract_addr.code_size()); + assert_eq!(arb_test_addr.code_size(), 1); + assert_eq!(arb_test_addr.code(), [0xfe]); + assert_eq!(eth_precompile_addr.code_size(), 0); + assert_eq!(eth_precompile_addr.code(), []); + + let basefee = block::basefee(); + let chainid = block::chainid(); + let coinbase = block::coinbase(); + let gas_limit = block::gas_limit(); + let timestamp = block::timestamp(); + let address = contract::address(); + let sender = msg::sender(); + let value = msg::value(); + let origin = tx::origin(); + let gas_price = tx::gas_price(); + let ink_price = tx::ink_price(); + + let mut block_number = block::number(); + block_number -= 1; + + // Call burnArbGas + let gas_left_before = evm::gas_left(); + let ink_left_before = evm::ink_left(); + RawCall::new().call(arb_test_addr, burn_call_data)?; + let gas_left_after = evm::gas_left(); + let ink_left_after = evm::ink_left(); + + let mut output = vec![]; + output.extend(B256::from(U256::from(block_number))); + output.extend(B256::from(U256::from(chainid))); + output.extend(B256::from(basefee)); + output.extend(B256::from(gas_price)); + output.extend(B256::from(U256::from(gas_limit))); + output.extend(B256::from(value)); + output.extend(B256::from(U256::from(timestamp))); + output.extend(B256::from(address_balance)); + + output.extend(address.into_word()); + output.extend(sender.into_word()); + output.extend(origin.into_word()); + output.extend(coinbase.into_word()); + + output.extend(contract_codehash); + output.extend(arb_precompile_codehash); + output.extend(eth_precompile_codehash); + output.extend(code); + + output.extend(ink_price.to_be_bytes()); + output.extend(gas_left_before.to_be_bytes()); + output.extend(ink_left_before.to_be_bytes()); + output.extend(gas_left_after.to_be_bytes()); + output.extend(ink_left_after.to_be_bytes()); + Ok(output) +} diff --git a/arbitrator/stylus/tests/exit-early/exit-early.wat b/arbitrator/stylus/tests/exit-early/exit-early.wat new file mode 100644 index 000000000..f5ed37cd6 --- /dev/null +++ b/arbitrator/stylus/tests/exit-early/exit-early.wat @@ -0,0 +1,24 @@ +;; Copyright 2024, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "exit_early" (func $exit (param i32))) + (memory (export "memory") 1 1) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + ;; write args to offset 0 + (call $read_args (i32.const 0)) + + ;; set the args as the result + (call $write_result (i32.const 0) (local.get $args_len)) + + ;; exit with the status code given by the first byte + i32.const 0 + i32.load8_u + call $exit + + ;; unreachable + (i32.const 0xff) + ) +) diff --git a/arbitrator/stylus/tests/exit-early/panic-after-write.wat b/arbitrator/stylus/tests/exit-early/panic-after-write.wat new file mode 100644 index 000000000..8e993ffc5 --- /dev/null +++ b/arbitrator/stylus/tests/exit-early/panic-after-write.wat @@ -0,0 +1,19 @@ +;; Copyright 2024, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "exit_early" (func $exit (param i32))) + (memory (export "memory") 1 1) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + ;; write args to offset 0 + (call $read_args (i32.const 0)) + + ;; set the args as the result + (call $write_result (i32.const 0) (local.get $args_len)) + + ;; perform a hard revert (results should be discarded) + unreachable + ) +) diff --git a/arbitrator/stylus/tests/fallible/.cargo/config b/arbitrator/stylus/tests/fallible/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/fallible/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/fallible/Cargo.lock b/arbitrator/stylus/tests/fallible/Cargo.lock new file mode 100644 index 000000000..252edfbbf --- /dev/null +++ b/arbitrator/stylus/tests/fallible/Cargo.lock @@ -0,0 +1,543 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "fallible" +version = "0.1.0" +dependencies = [ + "stylus-sdk", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/fallible/Cargo.toml b/arbitrator/stylus/tests/fallible/Cargo.toml new file mode 100644 index 000000000..36d57c3f0 --- /dev/null +++ b/arbitrator/stylus/tests/fallible/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fallible" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/fallible/src/main.rs b/arbitrator/stylus/tests/fallible/src/main.rs new file mode 100644 index 000000000..a8ccd15cb --- /dev/null +++ b/arbitrator/stylus/tests/fallible/src/main.rs @@ -0,0 +1,16 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::stylus_proc::entrypoint; + +/// A program that will fail on certain inputs +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + if input[0] == 0 { + core::arch::wasm32::unreachable() + } else { + Ok(input) + } +} diff --git a/arbitrator/stylus/tests/grow/fixed.wat b/arbitrator/stylus/tests/grow/fixed.wat new file mode 100644 index 000000000..7d6cc3aff --- /dev/null +++ b/arbitrator/stylus/tests/grow/fixed.wat @@ -0,0 +1,25 @@ +;; Copyright 2023-2024, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "console" "tee_i32" (func $tee_i32 (param i32) (result i32))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + ;; fail to grow the memory a non-zero number of pages + i32.const -65537 + call $tee_i32 + memory.grow + call $tee_i32 + i32.const -1 + i32.eq + i32.eqz + (if (then unreachable)) + + ;; succeed growing 0 pages + i32.const 0 + memory.grow + call $tee_i32 + i32.eqz + i32.eqz + ) + (memory (export "memory") 0 0) +) diff --git a/arbitrator/stylus/tests/grow/grow-120.wat b/arbitrator/stylus/tests/grow/grow-120.wat new file mode 100644 index 000000000..a76c42085 --- /dev/null +++ b/arbitrator/stylus/tests/grow/grow-120.wat @@ -0,0 +1,9 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (func (export "user_entrypoint") (param $args_len i32) (result i32) + i32.const 0 + ) + (memory (export "memory") 120 120) +) diff --git a/arbitrator/stylus/tests/grow/grow-and-call.wat b/arbitrator/stylus/tests/grow/grow-and-call.wat new file mode 100644 index 000000000..6fbe7429f --- /dev/null +++ b/arbitrator/stylus/tests/grow/grow-and-call.wat @@ -0,0 +1,37 @@ +;; Copyright 2023-2024, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "pay_for_memory_grow" (func (param i32))) + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "msg_value" (func $msg_value (param i32))) + (import "vm_hooks" "call_contract" (func $call_contract (param i32 i32 i32 i32 i64 i32) (result i32))) + (import "console" "tee_i32" (func $tee (param i32) (result i32))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + + ;; store the target size argument at offset 0 + i32.const 0 + call $read_args + + ;; grow the extra pages + i32.const 0 + i32.load8_u + memory.grow + drop + + ;; copy the message value + i32.const 0x1000 + call $msg_value + + ;; static call target contract + i32.const 1 ;; address + i32.const 21 ;; calldata + (i32.sub (local.get $args_len) (i32.const 21)) ;; calldata len + i32.const 0x1000 ;; callvalue + i64.const -1 ;; all gas + i32.const 0x2000 ;; return_data_len ptr + call $call_contract + ) + (memory (export "memory") 4) +) diff --git a/arbitrator/stylus/tests/grow/mem-write.wat b/arbitrator/stylus/tests/grow/mem-write.wat new file mode 100644 index 000000000..ec6efd973 --- /dev/null +++ b/arbitrator/stylus/tests/grow/mem-write.wat @@ -0,0 +1,45 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "pay_for_memory_grow" (func $pay_for_memory_grow (param i32))) + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "console" "tee_i32" (func $tee_i32 (param i32) (result i32))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + local.get $args_len + i32.eqz + (if (then + ;; write an empty result to offset 0 + (call $write_result (i32.const 0) (i32.const 0)) + (return (i32.const 0)) + )) + + ;; grow 1 page so that we can read our args + i32.const 1 + memory.grow + drop + + ;; store the size argument at offset 0 + i32.const 0 + call $read_args + + ;; read the argument and grow the remainder + i32.const 0 + i32.load8_u + i32.const 1 + i32.sub + memory.grow + drop + + ;; write a result (should panic if out of bounds) + i32.const 1 + i32.load + i32.const 5 + i32.load + call $write_result + + i32.const 0 + ) + (memory (export "memory") 0) +) diff --git a/arbitrator/stylus/tests/keccak-100/Cargo.lock b/arbitrator/stylus/tests/keccak-100/Cargo.lock new file mode 100644 index 000000000..d3ff2a09a --- /dev/null +++ b/arbitrator/stylus/tests/keccak-100/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-100" +version = "0.1.0" +dependencies = [ + "sha3", + "stylus-sdk", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/keccak-100/Cargo.toml b/arbitrator/stylus/tests/keccak-100/Cargo.toml new file mode 100644 index 000000000..e63f34767 --- /dev/null +++ b/arbitrator/stylus/tests/keccak-100/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "keccak-100" +version = "0.1.0" +edition = "2021" + +[dependencies] +sha3 = "0.10.5" +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/keccak-100/src/main.rs b/arbitrator/stylus/tests/keccak-100/src/main.rs new file mode 100644 index 000000000..1f0f60285 --- /dev/null +++ b/arbitrator/stylus/tests/keccak-100/src/main.rs @@ -0,0 +1,23 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use sha3::{Digest, Keccak256}; +use stylus_sdk::stylus_proc::entrypoint; + +#[entrypoint] +fn user_main(_: Vec) -> Result, Vec> { + let mut data = [0; 32]; + for _ in 0..100 { + data = keccak(&data); + } + assert_ne!(data, [0; 32]); + Ok(data.as_ref().into()) +} + +fn keccak(preimage: &[u8]) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.update(preimage); + hasher.finalize().into() +} diff --git a/arbitrator/stylus/tests/keccak/Cargo.lock b/arbitrator/stylus/tests/keccak/Cargo.lock new file mode 100644 index 000000000..5b5344e94 --- /dev/null +++ b/arbitrator/stylus/tests/keccak/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.0" +dependencies = [ + "sha3", + "stylus-sdk", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak 0.1.4", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/keccak/Cargo.toml b/arbitrator/stylus/tests/keccak/Cargo.toml new file mode 100644 index 000000000..b9c4baa75 --- /dev/null +++ b/arbitrator/stylus/tests/keccak/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "keccak" +version = "0.1.0" +edition = "2021" + +[dependencies] +sha3 = "0.10.5" +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/keccak/src/main.rs b/arbitrator/stylus/tests/keccak/src/main.rs new file mode 100644 index 000000000..dd14dd30e --- /dev/null +++ b/arbitrator/stylus/tests/keccak/src/main.rs @@ -0,0 +1,26 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use sha3::{Digest, Keccak256}; +use stylus_sdk::{alloy_primitives, crypto, prelude::*}; + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let mut data = keccak(&input[1..]); + let rounds = input[0]; + for _ in 1..rounds { + let hash = keccak(&data); + assert_eq!(hash, crypto::keccak(data)); + assert_eq!(hash, alloy_primitives::keccak256(data)); + data = hash; + } + Ok(data.as_ref().into()) +} + +fn keccak(preimage: &[u8]) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.update(preimage); + hasher.finalize().into() +} diff --git a/arbitrator/stylus/tests/log/.cargo/config b/arbitrator/stylus/tests/log/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/log/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/log/Cargo.lock b/arbitrator/stylus/tests/log/Cargo.lock new file mode 100644 index 000000000..0bb2ca333 --- /dev/null +++ b/arbitrator/stylus/tests/log/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "log" +version = "0.1.0" +dependencies = [ + "hex", + "stylus-sdk", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/log/Cargo.toml b/arbitrator/stylus/tests/log/Cargo.toml new file mode 100644 index 000000000..f3bba1d09 --- /dev/null +++ b/arbitrator/stylus/tests/log/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "log" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } +hex = "0.4.3" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/log/src/main.rs b/arbitrator/stylus/tests/log/src/main.rs new file mode 100644 index 000000000..0ce6f95fe --- /dev/null +++ b/arbitrator/stylus/tests/log/src/main.rs @@ -0,0 +1,20 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{alloy_primitives::B256, evm, prelude::*}; + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let num_topics = input[0]; + let mut input = &input[1..]; + + let mut topics = vec![]; + for _ in 0..num_topics { + topics.push(B256::try_from(&input[..32]).unwrap()); + input = &input[32..]; + } + evm::raw_log(&topics, input).unwrap(); + Ok(vec![]) +} diff --git a/arbitrator/stylus/tests/math/Cargo.lock b/arbitrator/stylus/tests/math/Cargo.lock new file mode 100644 index 000000000..3bf041ecd --- /dev/null +++ b/arbitrator/stylus/tests/math/Cargo.lock @@ -0,0 +1,543 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "math" +version = "0.1.0" +dependencies = [ + "stylus-sdk", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/math/Cargo.toml b/arbitrator/stylus/tests/math/Cargo.toml new file mode 100644 index 000000000..5fa060723 --- /dev/null +++ b/arbitrator/stylus/tests/math/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "math" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["debug"] } + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/math/src/main.rs b/arbitrator/stylus/tests/math/src/main.rs new file mode 100644 index 000000000..59e2450d5 --- /dev/null +++ b/arbitrator/stylus/tests/math/src/main.rs @@ -0,0 +1,39 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{ + alloy_primitives::{b256, B256}, + prelude::*, + ArbResult, +}; +extern crate alloc; + +#[link(wasm_import_module = "vm_hooks")] +extern "C" { + fn math_div(value: *mut u8, divisor: *const u8); + fn math_mod(value: *mut u8, modulus: *const u8); + fn math_pow(value: *mut u8, exponent: *const u8); + fn math_add_mod(value: *mut u8, addend: *const u8, modulus: *const u8); + fn math_mul_mod(value: *mut u8, multiplier: *const u8, modulus: *const u8); +} + +#[entrypoint] +fn user_main(_: Vec) -> ArbResult { + let mut value = b256!("eddecf107b5740cef7f5a01e3ea7e287665c4e75a8eb6afae2fda2e3d4367786"); + let unknown = b256!("c6178c2de1078cd36c3bd302cde755340d7f17fcb3fcc0b9c333ba03b217029f"); + let ed25519 = b256!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + + let part_1 = b256!("000000000000000000000000000000000000000000000000eddecf107b5740ce"); + let part_2 = b256!("000000000000000000000000000000000000000000000000fffffffefffffc2f"); + let part_3 = b256!("000000000000000000000000000000000000000000000000c6178c2de1078cd3"); + unsafe { + math_mul_mod(value.as_mut_ptr(), unknown.as_ptr(), ed25519.as_ptr()); + math_add_mod(value.as_mut_ptr(), ed25519.as_ptr(), unknown.as_ptr()); + math_div(value.as_mut_ptr(), part_1.as_ptr()); + math_pow(value.as_mut_ptr(), part_2.as_ptr()); + math_mod(value.as_mut_ptr(), part_3.as_ptr()); + Ok(value.0.into()) + } +} diff --git a/arbitrator/stylus/tests/memory.wat b/arbitrator/stylus/tests/memory.wat new file mode 100644 index 000000000..2c867c35b --- /dev/null +++ b/arbitrator/stylus/tests/memory.wat @@ -0,0 +1,59 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "pay_for_memory_grow" (func (param i32))) + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (local $size i32) (local $step i32) + + ;; store the target size argument at offset 0 + i32.const 0 + call $read_args + + ;; copy the target size + i32.const 0 + i32.load8_u + local.set $size + + ;; copy the step size + i32.const 1 + i32.load8_u + local.set $step + + ;; grow until equal to the target size + (loop $loop + + ;; grow by $step, shrinking if needed + (i32.add (local.get $step) (memory.size)) + local.get $size + i32.gt_u + (if (then + (i32.sub (local.get $size) (memory.size)) + local.set $step + )) + + (memory.grow (local.get $step)) + drop + + ;; loop if too small + (i32.lt_u (memory.size) (local.get $size)) + br_if $loop + ) + + ;; store the memory size at offset 0 + i32.const 0 + memory.size + i32.store + + ;; make that single byte the return data + i32.const 0 + i32.const 1 + call $write_result + + ;; return success + i32.const 0 + ) + (memory (export "memory") 1 128) +) diff --git a/arbitrator/stylus/tests/memory2.wat b/arbitrator/stylus/tests/memory2.wat new file mode 100644 index 000000000..f0001ad51 --- /dev/null +++ b/arbitrator/stylus/tests/memory2.wat @@ -0,0 +1,12 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "pay_for_memory_grow" (func $pay_for_memory_grow (param i32))) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (call $pay_for_memory_grow (i32.const 0)) + (call $pay_for_memory_grow (i32.sub (i32.const 0) (i32.const 1))) + i32.const 0 + ) + (memory (export "memory") 0) +) diff --git a/arbitrator/stylus/tests/module-mod.wat b/arbitrator/stylus/tests/module-mod.wat new file mode 100644 index 000000000..b8e856bab --- /dev/null +++ b/arbitrator/stylus/tests/module-mod.wat @@ -0,0 +1,9 @@ +;; Copyright 2022, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (import "test" "noop" (func)) + (memory (export "memory") 0 0) + (func (export "void")) + (func (export "more") (param i32 i64) (result f32) + unreachable)) diff --git a/arbitrator/stylus/tests/multicall/.cargo/config b/arbitrator/stylus/tests/multicall/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/multicall/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/multicall/Cargo.lock b/arbitrator/stylus/tests/multicall/Cargo.lock new file mode 100644 index 000000000..ca70689bf --- /dev/null +++ b/arbitrator/stylus/tests/multicall/Cargo.lock @@ -0,0 +1,838 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "proptest", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + +[[package]] +name = "multicall" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "hex", + "mini-alloc", + "stylus-sdk", + "wee_alloc", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/multicall/Cargo.toml b/arbitrator/stylus/tests/multicall/Cargo.toml new file mode 100644 index 000000000..3bc48c682 --- /dev/null +++ b/arbitrator/stylus/tests/multicall/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "multicall" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy-primitives = "0.3.1" +alloy-sol-types = "0.3.1" +mini-alloc = "0.4.2" +stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["reentrant"] } +hex = "0.4.3" +wee_alloc = "0.4.5" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] diff --git a/arbitrator/stylus/tests/multicall/src/main.rs b/arbitrator/stylus/tests/multicall/src/main.rs new file mode 100644 index 000000000..fd6929b8f --- /dev/null +++ b/arbitrator/stylus/tests/multicall/src/main.rs @@ -0,0 +1,121 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +extern crate alloc; + +use stylus_sdk::{ + storage::{StorageCache, GlobalStorage}, + alloy_primitives::{Address, B256}, + alloy_sol_types::sol, + call::RawCall, + console, + evm, + prelude::*, +}; + +use wee_alloc::WeeAlloc; + +#[global_allocator] +static ALLOC: WeeAlloc = WeeAlloc::INIT; + +sol!{ + event Called(address addr, uint8 count, bool success, bytes return_data); + event Storage(bytes32 slot, bytes32 data, bool write); +} + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let mut input = input.as_slice(); + let count = input[0]; + input = &input[1..]; + + // combined output of all calls + let mut output = vec![]; + + console!("Performing {count} action(s)"); + for _ in 0..count { + let length = u32::from_be_bytes(input[..4].try_into().unwrap()) as usize; + input = &input[4..]; + + let next = &input[length..]; + let mut curr = &input[..length]; + + let kind = curr[0]; + curr = &curr[1..]; + + if kind & 0xf0 == 0 { + // caller + let mut value = None; + if kind & 0x3 == 0 { + value = Some(B256::try_from(&curr[..32]).unwrap()); + curr = &curr[32..]; + }; + + let addr = Address::try_from(&curr[..20]).unwrap(); + let data = &curr[20..]; + match value { + Some(value) if !value.is_zero() => console!( + "Calling {addr} with {} bytes and value {} {kind}", + data.len(), + hex::encode(value) + ), + _ => console!("Calling {addr} with {} bytes {kind}", curr.len()), + } + + let raw_call = match kind & 0x3 { + 0 => RawCall::new_with_value(value.unwrap_or_default().into()), + 1 => RawCall::new_delegate(), + 2 => RawCall::new_static(), + x => panic!("unknown call kind {x}"), + }; + let (success, return_data) = match unsafe { raw_call.call(addr, data) } { + Ok(return_data) => (true, return_data), + Err(revert_data) => { + if kind & 0x4 == 0 { + return Err(revert_data) + } + (false, vec![]) + }, + }; + + if !return_data.is_empty() { + console!("Contract {addr} returned {} bytes", return_data.len()); + } + if kind & 0x8 != 0 { + evm::log(Called { addr, count, success, return_data: return_data.clone() }) + } + output.extend(return_data); + } else if kind & 0xf0 == 0x10 { + // storage + let slot = B256::try_from(&curr[..32]).unwrap(); + curr = &curr[32..]; + let data; + let write; + if kind & 0x7 == 0 { + console!("writing slot {}", curr.len()); + data = B256::try_from(&curr[..32]).unwrap(); + write = true; + unsafe { StorageCache::set_word(slot.into(), data.into()) }; + StorageCache::flush(); + } else if kind & 0x7 == 1{ + console!("reading slot"); + write = false; + data = StorageCache::get_word(slot.into()); + output.extend(data.clone()); + } else { + panic!("unknown storage kind {kind}") + } + if kind & 0x8 != 0 { + console!("slot: {}, data: {}, write {write}", slot, data); + evm::log(Storage { slot: slot.into(), data: data.into(), write }) + } + } else { + panic!("unknown action {kind}") + } + input = next; + } + + Ok(output) +} diff --git a/arbitrator/stylus/tests/read-return-data/.cargo/config b/arbitrator/stylus/tests/read-return-data/.cargo/config new file mode 100644 index 000000000..aa59d2ee1 --- /dev/null +++ b/arbitrator/stylus/tests/read-return-data/.cargo/config @@ -0,0 +1,3 @@ +[build] +target = "wasm32-unknown-unknown" + diff --git a/arbitrator/stylus/tests/read-return-data/Cargo.lock b/arbitrator/stylus/tests/read-return-data/Cargo.lock new file mode 100644 index 000000000..2d551af6e --- /dev/null +++ b/arbitrator/stylus/tests/read-return-data/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "read-return-data" +version = "0.1.0" +dependencies = [ + "hex", + "stylus-sdk", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/read-return-data/Cargo.toml b/arbitrator/stylus/tests/read-return-data/Cargo.toml new file mode 100644 index 000000000..e011ba8c3 --- /dev/null +++ b/arbitrator/stylus/tests/read-return-data/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "read-return-data" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } +hex = "0.4.3" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +# uncomment to optimize for size +# opt-level = "z" + +[workspace] + diff --git a/arbitrator/stylus/tests/read-return-data/src/main.rs b/arbitrator/stylus/tests/read-return-data/src/main.rs new file mode 100644 index 000000000..3f13deff1 --- /dev/null +++ b/arbitrator/stylus/tests/read-return-data/src/main.rs @@ -0,0 +1,65 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{ + alloy_primitives::{b256, Address}, + call::RawCall, + console, contract, + prelude::*, +}; + +macro_rules! error { + ($($msg:tt)*) => {{ + console!($($msg)*); + panic!("invalid data") + }}; +} + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let mut call_data = input.as_slice(); + let mut read = || { + let x = usize::from_be_bytes(call_data[..4].try_into().unwrap()); + call_data = &call_data[4..]; + x + }; + + let call_type = read(); + let offset = read(); + let size = read(); + let expected_size = read(); + let count = read(); + + // Call identity precompile to test return data + let precompile: Address = Address::from_word(b256!( + "0000000000000000000000000000000000000000000000000000000000000004" + )); + + let safe_offset = offset.min(call_data.len()); + + if call_type == 2 { + RawCall::new() + .limit_return_data(offset, size) + .call(precompile, call_data)?; + } + + for _ in 0..count { + let data = match call_type { + 0 => RawCall::new().call(precompile, call_data)?, + 1 => RawCall::new() + .limit_return_data(offset, size) + .call(precompile, call_data)?, + 2 => contract::read_return_data(offset, Some(size)), + _ => error!("unknown call_type {call_type}"), + }; + + let expected_data = &call_data[safe_offset..][..expected_size]; + if data != expected_data { + error!("call_type: {call_type}, calldata: {call_data:?}, offset: {offset}, size: {size} → {data:?} {expected_data:?}"); + } + } + + Ok(vec![]) +} diff --git a/arbitrator/stylus/tests/sdk-storage/.cargo/config b/arbitrator/stylus/tests/sdk-storage/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/sdk-storage/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/sdk-storage/Cargo.lock b/arbitrator/stylus/tests/sdk-storage/Cargo.lock new file mode 100644 index 000000000..778a091be --- /dev/null +++ b/arbitrator/stylus/tests/sdk-storage/Cargo.lock @@ -0,0 +1,600 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "sdk-storage" +version = "0.1.0" +dependencies = [ + "hex", + "mini-alloc", + "stylus-sdk", + "wee_alloc", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/sdk-storage/Cargo.toml b/arbitrator/stylus/tests/sdk-storage/Cargo.toml new file mode 100644 index 000000000..c136762b5 --- /dev/null +++ b/arbitrator/stylus/tests/sdk-storage/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sdk-storage" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk.path = "../../../langs/rust/stylus-sdk" +mini-alloc.path = "../../../langs/rust/mini-alloc" +hex = "0.4.3" +wee_alloc = "0.4.5" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" +opt-level = "s" + +[workspace] diff --git a/arbitrator/stylus/tests/sdk-storage/src/main.rs b/arbitrator/stylus/tests/sdk-storage/src/main.rs new file mode 100644 index 000000000..4bfe8b602 --- /dev/null +++ b/arbitrator/stylus/tests/sdk-storage/src/main.rs @@ -0,0 +1,323 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{ + alloy_primitives::{Address, Signed, Uint, B256, I32, U16, U256, U64, U8}, + prelude::*, +}; +use mini_alloc::MiniAlloc; + +#[global_allocator] +static ALLOC: MiniAlloc = MiniAlloc::INIT; + +sol_storage! { + pub struct Contract { + bool flag; + address owner; + address other; + Struct sub; + Struct[] structs; + uint64[] vector; + uint40[][] nested; + bytes bytes_full; + bytes bytes_long; + string chars; + Maps maps; + Arrays arrays; + } + + #[derive(Erase)] + pub struct Struct { + uint16 num; + int32 other; + bytes32 word; + } + + pub struct Maps { + mapping(uint256 => address) basic; + mapping(address => bool[]) vects; + mapping(int32 => address)[] array; + mapping(bytes1 => mapping(bool => uint256)) nested; + mapping(string => Struct) structs; + } + + pub struct Arrays { + string[4] strings; + + uint8 spacer; + uint24[5] packed; + uint8 trail; + + address[2] spill; + uint8[2][4] matrix; + int96[4][] vector; + int96[][4] vectors; + Struct[3] structs; + } +} + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let contract = unsafe { Contract::new(U256::ZERO, 0) }; + let selector = u32::from_be_bytes(input[0..4].try_into().unwrap()); + match selector { + 0xf809f205 => populate(contract), + 0xa7f43779 => remove(contract), + _ => panic!("unknown method"), + } + Ok(vec![]) +} + +fn populate(mut contract: Contract) { + // test primitives + let owner = Address::with_last_byte(0x70); + contract.flag.set(true); + contract.owner.set(owner); + assert_eq!(contract.owner.get(), owner); + + let mut setter = contract.other.load_mut(); + setter.set(Address::with_last_byte(0x30)); + + // test structs + contract.sub.num.set(U16::from(32)); + contract.sub.other.set(I32::MAX); + contract.sub.word.set(U256::from(64).into()); + assert_eq!(contract.sub.num.get(), U16::from(32)); + assert_eq!(contract.sub.other.get(), I32::MAX); + assert_eq!(contract.sub.word.get(), B256::from(U256::from(64))); + + // test primitive vectors + let mut vector = contract.vector; + for i in (0..32).map(U64::from) { + vector.push(i); + } + for i in (0..32).map(U64::from) { + assert_eq!(vector.get(i), Some(i)); + } + let value = U64::from(77); + let mut setter = vector.get_mut(7).unwrap(); + setter.set(value); + assert_eq!(setter.get(), value); + + // test nested vectors + let mut nested = contract.nested; + for w in 0..10 { + let mut inner = nested.grow(); + for i in 0..w { + inner.push(Uint::from(i)); + } + assert_eq!(inner.len(), w); + assert_eq!(nested.len(), w + 1); + } + for w in 0..10 { + let mut inner = nested.get_mut(w).unwrap(); + + for i in 0..w { + let value = inner.get(i).unwrap() * Uint::from(2); + let mut setter = inner.get_mut(i).unwrap(); + setter.set(value); + assert_eq!(inner.get(i), Some(value)); + } + } + + // test bytes + let mut bytes_full = contract.bytes_full; + let mut bytes_long = contract.bytes_long; + + for i in 0..31 { + bytes_full.push(i); + } + for i in 0..80 { + bytes_long.push(i); + } + for i in 0..31 { + assert_eq!(bytes_full.get(i), Some(i)); + } + for i in 0..80 { + let setter = bytes_long.get_mut(i).unwrap(); + assert_eq!(setter.get()[0], i); + } + assert_eq!(bytes_full.get(32), None); + assert_eq!(bytes_long.get(80), None); + + // test strings + let mut chars = contract.chars; + assert!(chars.is_empty() && chars.len() == 0); + assert_eq!(chars.get_string(), ""); + for c in "arbitrum stylus".chars() { + chars.push(c); + } + assert_eq!(chars.get_string(), "arbitrum stylus"); + + // test basic maps + let maps = contract.maps; + let mut basic = maps.basic; + for i in (0..16).map(U256::from) { + basic.insert(i, Address::from_word(B256::from(i))); + } + for i in 0..16 { + assert_eq!(basic.get(U256::from(i)), Address::with_last_byte(i)); + } + assert_eq!(basic.get(U256::MAX), Address::ZERO); + + // test map of vectors + let mut vects = maps.vects; + for a in 0..4 { + let mut bools = vects.setter(Address::with_last_byte(a)); + for _ in 0..=a { + bools.push(true) + } + } + + // test vector of maps + let mut array = maps.array; + for i in 0..4 { + let mut map = array.grow(); + let value = I32::from_le_bytes::<4>((i as u32).to_le_bytes()); + map.insert(value, Address::with_last_byte(i)); + } + + // test maps of maps + let mut nested = maps.nested; + for i in 0..4 { + let mut inner = nested.setter(U8::from(i).into()); + let mut value = inner.setter(U8::from((i % 2 == 0) as u8)); + value.set(Uint::from(i + 1)); + } + + // test map of structs (TODO: direct assignment) + let mut structs = maps.structs; + let mut entry = structs.setter("stylus".to_string()); + entry.num.set(contract.sub.num.get()); + entry.other.set(contract.sub.other.get()); + entry.word.set(contract.sub.word.get()); + + // test vec of structs + let mut structs = contract.structs; + for _ in 0..4 { + let mut entry = structs.grow(); + entry.num.set(contract.sub.num.get()); + entry.other.set(contract.sub.other.get()); + entry.word.set(contract.sub.word.get()); + } + + // test fixed arrays + let mut arrays = contract.arrays; + let mut slot = arrays.strings.setter(2).unwrap(); + slot.set_str("L2 is for you!"); + + // test packed arrays + for i in 0..5 { + let mut slot = arrays.packed.get_mut(i).unwrap(); + slot.set(Uint::from(i)); + } + + // test arrays that don't fit into a single word + for i in 0..2 { + let mut slot = arrays.spill.get_mut(i).unwrap(); + slot.set(Address::with_last_byte(i as u8)); + } + + // test 2d arrays + let mut matrix = arrays.matrix; + for i in 0..4 { + let mut inner = matrix.get_mut(i).unwrap(); + let mut slot = inner.get_mut(0).unwrap(); + slot.set(U8::from(i)); + + let value = slot.get(); + let mut slot = inner.get_mut(1).unwrap(); + slot.set(value + U8::from(1)); + } + + // test vector of arrays + for _ in 0..3 { + let mut fixed = arrays.vector.grow(); + for i in 0..4 { + let mut slot = fixed.get_mut(i).unwrap(); + slot.set(Signed::from_raw(Uint::from(i))); + } + } + + // test array of vectors + for w in 0..4 { + let mut vector = arrays.vectors.setter(w).unwrap(); + for i in 0..4 { + vector.push(Signed::from_raw(Uint::from(i))); + } + } + + // test array of structs + for i in 0..3 { + let mut entry = arrays.structs.get_mut(i).unwrap(); + + entry.num.set(contract.sub.num.get()); + entry.other.set(contract.sub.other.get()); + entry.word.set(contract.sub.word.get()); + } +} + +fn remove(mut contract: Contract) { + // pop all elements + let mut bytes_full = contract.bytes_full; + while let Some(value) = bytes_full.pop() { + assert_eq!(value as usize, bytes_full.len()); + } + assert!(bytes_full.is_empty()); + + // pop until representation change + let mut bytes_long = contract.bytes_long; + while bytes_long.len() > 16 { + assert!(bytes_long.pop().is_some()); + } + + // overwrite strings + let mut chars = contract.chars; + let spiders = r"/\oo/\ //\\(oo)//\\ /\oo/\"; + chars.set_str(spiders.repeat(6)); + chars.set_str("wasm is cute <3"); + + // pop all elements + let mut vector = contract.vector; + while let Some(x) = vector.pop() { + assert!(x == U64::from(vector.len()) || x == U64::from(77)); + } + assert!(vector.is_empty() && vector.len() == 0); + + // erase inner vectors + let mut nested = contract.nested; + while nested.len() > 2 { + nested.erase_last(); + } + nested.shrink().map(|mut x| x.erase()); + + // erase map elements + let maps = contract.maps; + let mut basic = maps.basic; + for i in 0..7 { + basic.delete(Uint::from(i)); + } + let value = basic.take(Uint::from(7)); + assert_eq!(value, Address::with_last_byte(7)); + let value = basic.replace(Uint::from(8), Address::with_last_byte(32)); + assert_eq!(value, Address::with_last_byte(8)); + + // erase vectors in map + let mut vects = maps.vects; + for a in 0..3 { + let mut bools = vects.setter(Address::with_last_byte(a)); + bools.erase(); + } + vects.delete(Address::with_last_byte(3)); + + // erase a struct + contract.structs.erase_last(); + + // erase fixed arrays + contract.arrays.matrix.erase(); + contract.arrays.vector.erase(); + contract.arrays.vectors.erase(); + contract.arrays.structs.erase(); +} diff --git a/arbitrator/stylus/tests/start.wat b/arbitrator/stylus/tests/start.wat new file mode 100644 index 000000000..9d74df4c1 --- /dev/null +++ b/arbitrator/stylus/tests/start.wat @@ -0,0 +1,18 @@ +;; Copyright 2022, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (global $status (export "status") (mut i32) (i32.const 10)) + (memory 0 0) + (export "memory" (memory 0)) + (type $void (func (param) (result))) + (func $start (export "move_me") (type $void) + get_global $status + i32.const 1 + i32.add + set_global $status ;; increment the global + ) + (func (export "user_entrypoint") (param $args_len i32) (result i32) + (i32.const 0) + ) + (start $start)) diff --git a/arbitrator/stylus/tests/storage/.cargo/config b/arbitrator/stylus/tests/storage/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/arbitrator/stylus/tests/storage/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/arbitrator/stylus/tests/storage/Cargo.lock b/arbitrator/stylus/tests/storage/Cargo.lock new file mode 100644 index 000000000..a686950b2 --- /dev/null +++ b/arbitrator/stylus/tests/storage/Cargo.lock @@ -0,0 +1,543 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "storage" +version = "0.1.0" +dependencies = [ + "stylus-sdk", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/arbitrator/stylus/tests/storage/Cargo.toml b/arbitrator/stylus/tests/storage/Cargo.toml new file mode 100644 index 000000000..0137fbd27 --- /dev/null +++ b/arbitrator/stylus/tests/storage/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "storage" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk" } + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" + +[workspace] diff --git a/arbitrator/stylus/tests/storage/src/main.rs b/arbitrator/stylus/tests/storage/src/main.rs new file mode 100644 index 000000000..e19462991 --- /dev/null +++ b/arbitrator/stylus/tests/storage/src/main.rs @@ -0,0 +1,48 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{ + alloy_primitives::B256, + console, + storage::{StorageCache, GlobalStorage}, + stylus_proc::entrypoint, +}; + +#[link(wasm_import_module = "vm_hooks")] +extern "C" { + fn transient_load_bytes32(key: *const u8, dest: *mut u8); + fn transient_store_bytes32(key: *const u8, value: *const u8); +} + +#[entrypoint] +fn user_main(input: Vec) -> Result, Vec> { + let slot = B256::try_from(&input[1..33]).unwrap(); + + Ok(match input[0] { + 0 => { + console!("read {slot}"); + let data = StorageCache::get_word(slot.into()); + console!("value {data}"); + data.0.into() + } + 1 => { + console!("write {slot}"); + let data = B256::try_from(&input[33..]).unwrap(); + unsafe { StorageCache::set_word(slot.into(), data) }; + console!(("value {data}")); + vec![] + } + 2 => unsafe { + let mut data = [0; 32]; + transient_load_bytes32(slot.as_ptr(), data.as_mut_ptr()); + data.into() + } + _ => unsafe { + let data = B256::try_from(&input[33..]).unwrap(); + transient_store_bytes32(slot.as_ptr(), data.as_ptr()); + vec![] + } + }) +} diff --git a/arbitrator/stylus/tests/test.wat b/arbitrator/stylus/tests/test.wat new file mode 100644 index 000000000..caef666ea --- /dev/null +++ b/arbitrator/stylus/tests/test.wat @@ -0,0 +1,5 @@ +;; Copyright 2022, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (func (export "test__noop"))) diff --git a/arbitrator/stylus/tests/timings/Cargo.lock b/arbitrator/stylus/tests/timings/Cargo.lock new file mode 100644 index 000000000..61e05834b --- /dev/null +++ b/arbitrator/stylus/tests/timings/Cargo.lock @@ -0,0 +1,746 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "alloy-primitives" +version = "0.2.0" +source = "git+https://github.com/rachel-bousfield/alloy-core.git?branch=native-keccak#fd40d0a68a538a7583c8094c97e77fa2d9e49eb0" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "proptest", + "ruint2", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.2.0" +source = "git+https://github.com/rachel-bousfield/alloy-core.git?branch=native-keccak#fd40d0a68a538a7583c8094c97e77fa2d9e49eb0" +dependencies = [ + "arrayvec", + "bytes", + "smol_str", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" + +[[package]] +name = "keccak" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ruint2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b066b8e4fcea7fae86b6932d2449670b6b5545b7e8407841b2d3a916ff2a9f86" +dependencies = [ + "derive_more", + "ruint2-macro", + "rustc_version", + "thiserror", +] + +[[package]] +name = "ruint2-macro" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dc553bc0cf4512a8b96caa2e21ed5f6e4b66bf28a1bd08fd9eb07c0b39b28c" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" + +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + +[[package]] +name = "stylus-sdk" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "hex", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "thiserror" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "timings" +version = "0.1.0" +dependencies = [ + "sha3", + "stylus-sdk", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/arbitrator/stylus/tests/timings/block_basefee.wat b/arbitrator/stylus/tests/timings/block_basefee.wat new file mode 100644 index 000000000..ef5e66b01 --- /dev/null +++ b/arbitrator/stylus/tests/timings/block_basefee.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "block_basefee" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/block_coinbase.wat b/arbitrator/stylus/tests/timings/block_coinbase.wat new file mode 100644 index 000000000..f7d664997 --- /dev/null +++ b/arbitrator/stylus/tests/timings/block_coinbase.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "block_coinbase" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/block_gas_limit.wat b/arbitrator/stylus/tests/timings/block_gas_limit.wat new file mode 100644 index 000000000..2f5fc093b --- /dev/null +++ b/arbitrator/stylus/tests/timings/block_gas_limit.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "block_gas_limit" (func $test (result i64))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/block_number.wat b/arbitrator/stylus/tests/timings/block_number.wat new file mode 100644 index 000000000..b16de481a --- /dev/null +++ b/arbitrator/stylus/tests/timings/block_number.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "block_number" (func $test (result i64))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/block_timestamp.wat b/arbitrator/stylus/tests/timings/block_timestamp.wat new file mode 100644 index 000000000..164011bea --- /dev/null +++ b/arbitrator/stylus/tests/timings/block_timestamp.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "block_timestamp" (func $test (result i64))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/chainid.wat b/arbitrator/stylus/tests/timings/chainid.wat new file mode 100644 index 000000000..e9a4a0205 --- /dev/null +++ b/arbitrator/stylus/tests/timings/chainid.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "chainid" (func $test (result i64))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/contract_address.wat b/arbitrator/stylus/tests/timings/contract_address.wat new file mode 100644 index 000000000..8efd43998 --- /dev/null +++ b/arbitrator/stylus/tests/timings/contract_address.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "contract_address" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/evm_gas_left.wat b/arbitrator/stylus/tests/timings/evm_gas_left.wat new file mode 100644 index 000000000..c0a123752 --- /dev/null +++ b/arbitrator/stylus/tests/timings/evm_gas_left.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "evm_gas_left" (func $test (result i64))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/evm_ink_left.wat b/arbitrator/stylus/tests/timings/evm_ink_left.wat new file mode 100644 index 000000000..8b398b2af --- /dev/null +++ b/arbitrator/stylus/tests/timings/evm_ink_left.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "evm_ink_left" (func $test (result i64))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/keccak.wat b/arbitrator/stylus/tests/timings/keccak.wat new file mode 100644 index 000000000..7fee05caa --- /dev/null +++ b/arbitrator/stylus/tests/timings/keccak.wat @@ -0,0 +1,35 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "native_keccak256" (func $test (param i32 i32 i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + (call $test (i32.const 0) (local.get $args_len) (i32.const 0)) + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/msg_sender.wat b/arbitrator/stylus/tests/timings/msg_sender.wat new file mode 100644 index 000000000..e0da7d38d --- /dev/null +++ b/arbitrator/stylus/tests/timings/msg_sender.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "msg_sender" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/msg_value.wat b/arbitrator/stylus/tests/timings/msg_value.wat new file mode 100644 index 000000000..d7b73ed44 --- /dev/null +++ b/arbitrator/stylus/tests/timings/msg_value.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "msg_value" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/null_host.wat b/arbitrator/stylus/tests/timings/null_host.wat new file mode 100644 index 000000000..9c73ddc29 --- /dev/null +++ b/arbitrator/stylus/tests/timings/null_host.wat @@ -0,0 +1,35 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "debug" "null_host" (func $test)) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/read_args.wat b/arbitrator/stylus/tests/timings/read_args.wat new file mode 100644 index 000000000..d2f76fb89 --- /dev/null +++ b/arbitrator/stylus/tests/timings/read_args.wat @@ -0,0 +1,34 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + (call $read_args (i32.const 0)) + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 1 ;; skip the first + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/return_data_size.wat b/arbitrator/stylus/tests/timings/return_data_size.wat new file mode 100644 index 000000000..3b8f6ab66 --- /dev/null +++ b/arbitrator/stylus/tests/timings/return_data_size.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "return_data_size" (func $test (result i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/tx_gas_price.wat b/arbitrator/stylus/tests/timings/tx_gas_price.wat new file mode 100644 index 000000000..460823948 --- /dev/null +++ b/arbitrator/stylus/tests/timings/tx_gas_price.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "tx_gas_price" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/tx_ink_price.wat b/arbitrator/stylus/tests/timings/tx_ink_price.wat new file mode 100644 index 000000000..5650a8bca --- /dev/null +++ b/arbitrator/stylus/tests/timings/tx_ink_price.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "tx_ink_price" (func $test (result i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + call $test + drop + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/tx_origin.wat b/arbitrator/stylus/tests/timings/tx_origin.wat new file mode 100644 index 000000000..c092b15c4 --- /dev/null +++ b/arbitrator/stylus/tests/timings/tx_origin.wat @@ -0,0 +1,36 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (import "vm_hooks" "tx_origin" (func $test (param i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + i32.const 0 + call $test + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 0 + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/stylus/tests/timings/write_result.wat b/arbitrator/stylus/tests/timings/write_result.wat new file mode 100644 index 000000000..af2283f39 --- /dev/null +++ b/arbitrator/stylus/tests/timings/write_result.wat @@ -0,0 +1,34 @@ +;; Copyright 2023, Offchain Labs, Inc. +;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (memory (export "memory") 1 1) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $i i32) + + ;; write args to 0x0 + i32.const 0 + call $read_args + + ;; treat first 4 bytes as # of iterations + (i32.load (i32.const 0)) + local.set $i + + (loop + ;; call the test function + (call $write_result (i32.const 0) (local.get $args_len)) + + ;; decrement and loop + (i32.sub (local.get $i) (i32.const 1)) + local.tee $i + i32.const 1 ;; skip the last + i32.ne + br_if 0 + ) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/tools/module_roots/Cargo.lock b/arbitrator/tools/module_roots/Cargo.lock new file mode 100644 index 000000000..248a632d0 --- /dev/null +++ b/arbitrator/tools/module_roots/Cargo.lock @@ -0,0 +1,2232 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli 0.28.1", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arbutil" +version = "0.1.0" +dependencies = [ + "digest 0.10.7", + "eyre", + "fnv", + "hex", + "num-traits", + "num_enum", + "ruint2", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", + "siphasher", + "tiny-keccak", + "wasmparser", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blst" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "brotli" +version = "0.1.0" +dependencies = [ + "lazy_static", + "num_enum", + "wasmer", + "wee_alloc", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "c-kzg" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "serde", +] + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "corosensei" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "scopeguard", + "windows-sys", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" +dependencies = [ + "arrayvec", + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-egraph", + "cranelift-entity", + "cranelift-isle", + "gimli 0.26.2", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" + +[[package]] +name = "cranelift-egraph" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" +dependencies = [ + "cranelift-entity", + "fxhash", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "log", + "smallvec", +] + +[[package]] +name = "cranelift-entity" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" + +[[package]] +name = "cranelift-frontend" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core 0.20.8", + "darling_macro 0.20.8", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core 0.20.8", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] + +[[package]] +name = "dynasm" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dynasmrt" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +dependencies = [ + "byteorder", + "dynasm", + "memmap2 0.5.10", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enumset" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling 0.20.8", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown 0.14.3", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "module_roots" +version = "0.1.0" +dependencies = [ + "arbutil", + "eyre", + "prover", + "structopt", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-leb128" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a73b6c3a9ecfff12ad50dedba051ef838d2f478d938bb3e6b3842431028e62" +dependencies = [ + "arrayvec", + "nom", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prover" +version = "0.1.0" +dependencies = [ + "arbutil", + "bincode", + "brotli", + "c-kzg", + "derivative", + "digest 0.9.0", + "eyre", + "fnv", + "hex", + "itertools", + "lazy_static", + "libc", + "lru", + "nom", + "nom-leb128", + "num", + "num-derive", + "num-traits", + "parking_lot", + "rayon", + "rustc-demangle", + "serde", + "serde_json", + "serde_with", + "sha2 0.9.9", + "sha3 0.9.1", + "smallvec", + "static_assertions", + "structopt", + "wasmer", + "wasmer-compiler-singlepass", + "wasmer-types", + "wasmparser", + "wat", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rayon" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regalloc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "region" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ruint2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b066b8e4fcea7fae86b6932d2449670b6b5545b7e8407841b2d3a916ff2a9f86" +dependencies = [ + "derive_more", + "ruint2-macro", + "rustc_version", + "thiserror", +] + +[[package]] +name = "ruint2-macro" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dc553bc0cf4512a8b96caa2e21ed5f6e4b66bf28a1bd08fd9eb07c0b39b28c" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shared-buffer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" +dependencies = [ + "bytes", + "memmap2 0.6.2", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +dependencies = [ + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.5", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-encoder" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmer" +version = "4.2.8" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "derivative", + "indexmap 1.9.3", + "js-sys", + "more-asserts", + "rustc-demangle", + "serde", + "serde-wasm-bindgen", + "shared-buffer", + "target-lexicon", + "thiserror", + "tracing", + "wasm-bindgen", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-derive", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi", +] + +[[package]] +name = "wasmer-compiler" +version = "4.2.8" +dependencies = [ + "backtrace", + "bytes", + "cfg-if 1.0.0", + "enum-iterator", + "enumset", + "lazy_static", + "leb128", + "memmap2 0.5.10", + "more-asserts", + "region", + "rkyv", + "self_cell", + "shared-buffer", + "smallvec", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser", + "winapi", +] + +[[package]] +name = "wasmer-compiler-cranelift" +version = "4.2.8" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "gimli 0.26.2", + "more-asserts", + "rayon", + "smallvec", + "target-lexicon", + "tracing", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-compiler-singlepass" +version = "4.2.8" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "enumset", + "gimli 0.26.2", + "lazy_static", + "more-asserts", + "rayon", + "smallvec", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-derive" +version = "4.2.8" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wasmer-types" +version = "4.2.8" +dependencies = [ + "bytecheck", + "enum-iterator", + "enumset", + "indexmap 1.9.3", + "more-asserts", + "rkyv", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "wasmer-vm" +version = "4.2.8" +dependencies = [ + "backtrace", + "cc", + "cfg-if 1.0.0", + "corosensei", + "crossbeam-queue", + "dashmap", + "derivative", + "enum-iterator", + "fnv", + "indexmap 1.9.3", + "lazy_static", + "libc", + "mach", + "memoffset", + "more-asserts", + "region", + "scopeguard", + "thiserror", + "wasmer-types", + "winapi", +] + +[[package]] +name = "wasmparser" +version = "0.121.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" +dependencies = [ + "bitflags 2.5.0", + "indexmap 2.2.5", + "semver", +] + +[[package]] +name = "wast" +version = "64.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" +dependencies = [ + "wast", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] diff --git a/arbitrator/tools/module_roots/Cargo.toml b/arbitrator/tools/module_roots/Cargo.toml new file mode 100644 index 000000000..bb5ab1699 --- /dev/null +++ b/arbitrator/tools/module_roots/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "module_roots" +version = "0.1.0" +edition = "2021" + +[dependencies] +arbutil = { path = "../../arbutil/" } +prover = { path = "../../prover/" } +eyre = "0.6.5" +structopt = "0.3.23" + +[workspace] diff --git a/arbitrator/tools/module_roots/src/main.rs b/arbitrator/tools/module_roots/src/main.rs new file mode 100644 index 000000000..32e476484 --- /dev/null +++ b/arbitrator/tools/module_roots/src/main.rs @@ -0,0 +1,76 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use arbutil::Bytes32; +use eyre::{Result, WrapErr}; +use prover::{machine::GlobalState, utils::file_bytes, Machine}; +use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc}; +use structopt::StructOpt; + +#[derive(StructOpt)] +#[structopt(name = "module-roots")] +struct Opts { + #[structopt(long)] + binary: PathBuf, + #[structopt(long)] + stylus_modules: Vec, +} + +fn main() -> Result<()> { + let mut opts = Opts::from_args(); + + macro_rules! relocate { + ($file:expr) => { + let mut path = PathBuf::from("../../../"); + path.push(&$file); + *$file = path; + }; + } + relocate!(&mut opts.binary); + + let mut mach = Machine::from_paths( + &[], + &opts.binary, + true, + true, + true, + true, + true, + GlobalState::default(), + HashMap::default(), + Arc::new(|_, _, _| panic!("tried to read preimage")), + )?; + + let mut stylus = vec![]; + for module in &mut opts.stylus_modules { + relocate!(module); + let error = || format!("failed to read module at {}", module.to_string_lossy()); + let wasm = file_bytes(&module).wrap_err_with(error)?; + let code = &Bytes32::default(); + let hash = mach + .add_program(&wasm, code, 1, true) + .wrap_err_with(error)?; + let name = module.file_stem().unwrap().to_string_lossy(); + stylus.push((name.to_owned(), hash)); + println!("{} {}", name, hash); + } + + let mut segment = 0; + for (name, root) in stylus { + println!(" (data (i32.const 0x{:03x})", segment); + println!(" \"{}\") ;; {}", pairs(root), name); + segment += 32; + } + Ok(()) +} + +fn pairs(text: D) -> String { + let mut out = String::new(); + let text = format!("{text}"); + let mut chars = text.chars(); + while let Some(a) = chars.next() { + let b = chars.next().unwrap(); + out += &format!("\\{a}{b}") + } + out +} diff --git a/arbitrator/tools/wasmer b/arbitrator/tools/wasmer new file mode 160000 index 000000000..6b15433d8 --- /dev/null +++ b/arbitrator/tools/wasmer @@ -0,0 +1 @@ +Subproject commit 6b15433d83f951555c24f0c56dc05e4751b0cc76 diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index b50aebb95..68972bcff 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -2,303 +2,2210 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arbcompress" +version = "0.1.0" +dependencies = [ + "brotli", + "caller-env", + "paste", +] + [[package]] name = "arbutil" version = "0.1.0" dependencies = [ - "digest", + "digest 0.10.7", + "eyre", + "fnv", + "hex", + "num-traits", "num_enum", - "sha2", - "sha3", + "ruint2", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", + "siphasher", + "tiny-keccak", + "wasmparser", ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "ark-bn254" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" dependencies = [ - "generic-array", + "ark-ec", + "ark-ff", + "ark-std", ] [[package]] -name = "brotli" -version = "0.1.0" +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "go-abi", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "ark-ff" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] [[package]] -name = "cpufeatures" -version = "0.2.9" +name = "ark-ff-asm" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "libc", + "quote", + "syn 1.0.109", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "ark-ff-macros" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ - "generic-array", - "typenum", + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "digest" -version = "0.10.7" +name = "ark-poly" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "block-buffer", - "crypto-common", + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "ark-serialize" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] [[package]] -name = "fnv" -version = "1.0.7" +name = "ark-serialize-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "generic-array" -version = "0.14.7" +name = "ark-std" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ - "typenum", - "version_check", + "num-traits", + "rand", ] [[package]] -name = "go-abi" -version = "0.1.0" +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] -name = "go-stub" -version = "0.1.0" +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "fnv", - "go-abi", - "rand", - "rand_pcg", + "hermit-abi 0.1.19", + "libc", + "winapi", ] [[package]] -name = "hashbrown" -version = "0.14.0" +name = "autocfg" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "host-io" -version = "0.1.0" +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "arbutil", - "go-abi", + "serde", ] [[package]] -name = "indexmap" -version = "2.0.0" +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "equivalent", - "hashbrown", + "funty", + "radium", + "tap", + "wyz", ] [[package]] -name = "keccak" -version = "0.1.4" +name = "block-buffer" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "cpufeatures", + "block-padding", + "generic-array", ] [[package]] -name = "libc" -version = "0.2.151" +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] -name = "memchr" -version = "2.5.0" +name = "brotli" +version = "0.1.0" +dependencies = [ + "lazy_static", + "num_enum", + "wee_alloc", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] -name = "num_enum" -version = "0.7.0" +name = "bytecheck" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ - "num_enum_derive", + "bytecheck_derive", + "ptr_meta", + "simdutf8", ] [[package]] -name = "num_enum_derive" -version = "0.7.0" +name = "bytecheck_derive" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] -name = "once_cell" -version = "1.18.0" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "bytes" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "caller-env" +version = "0.1.0" dependencies = [ - "once_cell", - "toml_edit", + "brotli", + "num_enum", + "rand", + "rand_pcg", ] [[package]] -name = "proc-macro2" -version = "1.0.66" +name = "cc" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5208975e568d83b6b05cc0a063c8e7e9acc2b43bee6da15616a5b73e109d7437" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "unicode-ident", + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] -name = "quote" -version = "1.0.32" +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ - "proc-macro2", + "libc", ] [[package]] -name = "rand" -version = "0.8.4" +name = "crc32fast" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "rand_core", + "cfg-if 1.0.0", ] [[package]] -name = "rand_core" -version = "0.6.3" +name = "crossbeam-channel" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "rand_pcg" -version = "0.3.1" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "rand_core", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "sha2" -version = "0.10.7" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "crossbeam-utils", ] [[package]] -name = "sha3" -version = "0.10.8" +name = "crossbeam-utils" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "digest", - "keccak", + "generic-array", + "typenum", ] [[package]] -name = "syn" -version = "2.0.32" +name = "darling" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core 0.20.8", + "darling_macro 0.20.8", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", - "unicode-ident", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] -name = "toml_datetime" -version = "0.6.3" +name = "darling_core" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.52", +] [[package]] -name = "toml_edit" -version = "0.19.14" +name = "darling_macro" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "indexmap", - "toml_datetime", - "winnow", + "darling_core 0.13.4", + "quote", + "syn 1.0.109", ] [[package]] -name = "typenum" -version = "1.16.0" +name = "darling_macro" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core 0.20.8", + "quote", + "syn 2.0.52", +] [[package]] -name = "unicode-ident" -version = "1.0.11" +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "digest" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] [[package]] -name = "wasi-stub" -version = "0.1.0" +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] [[package]] -name = "winnow" -version = "0.5.12" +name = "directories" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83817bbecf72c73bad717ee86820ebf286203d2e04c3951f3cd538869c897364" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ - "memchr", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enumset" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling 0.20.8", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "forward" +version = "0.1.0" +dependencies = [ + "eyre", + "structopt", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "host-io" +version = "0.1.0" +dependencies = [ + "arbutil", + "caller-env", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown 0.14.3", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-leb128" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a73b6c3a9ecfff12ad50dedba051ef838d2f478d938bb3e6b3842431028e62" +dependencies = [ + "arrayvec", + "nom", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "program-exec" +version = "0.1.0" + +[[package]] +name = "prover" +version = "0.1.0" +dependencies = [ + "arbutil", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "bincode", + "brotli", + "derivative", + "digest 0.9.0", + "eyre", + "fnv", + "hex", + "itertools", + "lazy_static", + "libc", + "lru", + "nom", + "nom-leb128", + "num", + "num-bigint", + "num-derive", + "num-traits", + "once_cell", + "parking_lot", + "rust-kzg-bn254", + "rustc-demangle", + "serde", + "serde_json", + "serde_with", + "sha2 0.9.9", + "sha3 0.9.1", + "smallvec", + "static_assertions", + "structopt", + "wasmer-types", + "wasmparser", + "wat", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ruint2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b066b8e4fcea7fae86b6932d2449670b6b5545b7e8407841b2d3a916ff2a9f86" +dependencies = [ + "derive_more", + "ruint2-macro", + "rustc_version", + "thiserror", +] + +[[package]] +name = "ruint2-macro" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dc553bc0cf4512a8b96caa2e21ed5f6e4b66bf28a1bd08fd9eb07c0b39b28c" + +[[package]] +name = "rust-kzg-bn254" +version = "0.2.0" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "byteorder", + "crossbeam-channel", + "directories", + "hex-literal", + "num-bigint", + "num-traits", + "num_cpus", + "rand", + "rayon", + "sha2 0.10.8", + "ureq", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.5", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "user-host" +version = "0.1.0" +dependencies = [ + "arbutil", + "caller-env", + "eyre", + "fnv", + "hex", + "prover", + "user-host-trait", + "wasmer-types", +] + +[[package]] +name = "user-host-trait" +version = "0.1.0" +dependencies = [ + "arbutil", + "caller-env", + "eyre", + "prover", + "ruint2", +] + +[[package]] +name = "user-test" +version = "0.1.0" +dependencies = [ + "arbutil", + "caller-env", + "eyre", + "fnv", + "hex", + "lazy_static", + "parking_lot", + "prover", + "user-host-trait", +] + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi-stub" +version = "0.1.0" +dependencies = [ + "caller-env", + "paste", + "wee_alloc", +] + +[[package]] +name = "wasm-encoder" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmer-types" +version = "4.2.8" +dependencies = [ + "bytecheck", + "enum-iterator", + "enumset", + "indexmap 1.9.3", + "more-asserts", + "rkyv", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "wasmparser" +version = "0.121.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" +dependencies = [ + "bitflags 2.5.0", + "indexmap 2.2.5", + "semver", +] + +[[package]] +name = "wast" +version = "201.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32" +dependencies = [ + "wast", +] + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", ] diff --git a/arbitrator/wasm-libraries/Cargo.toml b/arbitrator/wasm-libraries/Cargo.toml index 3179163a6..837df8f4d 100644 --- a/arbitrator/wasm-libraries/Cargo.toml +++ b/arbitrator/wasm-libraries/Cargo.toml @@ -1,8 +1,12 @@ [workspace] members = [ - "brotli", + "arbcompress", "wasi-stub", - "go-stub", - "go-abi", "host-io", + "user-host", + "user-host-trait", + "user-test", + "program-exec", + "forward", ] +resolver = "2" diff --git a/arbitrator/wasm-libraries/arbcompress/Cargo.toml b/arbitrator/wasm-libraries/arbcompress/Cargo.toml new file mode 100644 index 000000000..ec4c32c1e --- /dev/null +++ b/arbitrator/wasm-libraries/arbcompress/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "arbcompress" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +brotli.path = "../../brotli" +caller-env = { path = "../../caller-env/", features = ["static_caller"] } +paste = "1.0.14" diff --git a/arbitrator/wasm-libraries/brotli/build.rs b/arbitrator/wasm-libraries/arbcompress/build.rs similarity index 65% rename from arbitrator/wasm-libraries/brotli/build.rs rename to arbitrator/wasm-libraries/arbcompress/build.rs index 9cf73a4ec..1c42e27f5 100644 --- a/arbitrator/wasm-libraries/brotli/build.rs +++ b/arbitrator/wasm-libraries/arbcompress/build.rs @@ -1,6 +1,10 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + fn main() { // Tell Cargo that if the given file changes, to rerun this build script. println!("cargo:rustc-link-search=../../target/lib-wasm/"); + println!("cargo:rustc-link-search=../target/lib/"); println!("cargo:rustc-link-lib=static=brotlienc-static"); println!("cargo:rustc-link-lib=static=brotlidec-static"); println!("cargo:rustc-link-lib=static=brotlicommon-static"); diff --git a/arbitrator/wasm-libraries/arbcompress/src/lib.rs b/arbitrator/wasm-libraries/arbcompress/src/lib.rs new file mode 100644 index 000000000..fe54e667d --- /dev/null +++ b/arbitrator/wasm-libraries/arbcompress/src/lib.rs @@ -0,0 +1,45 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::missing_safety_doc)] // TODO: add safety docs + +use brotli::{BrotliStatus, Dictionary}; +use caller_env::{self, GuestPtr}; +use paste::paste; + +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + paste! { + $( + #[no_mangle] + pub unsafe extern "C" fn []($($arg_name : $arg_type),*) -> $return_type { + caller_env::brotli::$func_name( + &mut caller_env::static_caller::STATIC_MEM, + &mut caller_env::static_caller::STATIC_ENV, + $($arg_name),* + ) + } + )* + } + }; +} + +wrap! { + fn brotli_compress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32, + dictionary: Dictionary + ) -> BrotliStatus; + + fn brotli_decompress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + dictionary: Dictionary + ) -> BrotliStatus +} diff --git a/arbitrator/wasm-libraries/brotli/src/lib.rs b/arbitrator/wasm-libraries/brotli/src/lib.rs deleted file mode 100644 index 7e95d90ca..000000000 --- a/arbitrator/wasm-libraries/brotli/src/lib.rs +++ /dev/null @@ -1,84 +0,0 @@ -use go_abi::*; - -extern "C" { - pub fn BrotliDecoderDecompress( - encoded_size: usize, - encoded_buffer: *const u8, - decoded_size: *mut usize, - decoded_buffer: *mut u8, - ) -> u32; - - pub fn BrotliEncoderCompress( - quality: u32, - lgwin: u32, - mode: u32, - input_size: usize, - input_buffer: *const u8, - encoded_size: *mut usize, - encoded_buffer: *mut u8, - ) -> u32; -} - -const BROTLI_MODE_GENERIC: u32 = 0; -const BROTLI_RES_SUCCESS: u32 = 1; - -#[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbcompress_brotliDecompress( - sp: GoStack, -) { - //(inBuf []byte, outBuf []byte) int - let in_buf_ptr = sp.read_u64(0); - let in_buf_len = sp.read_u64(1); - let out_buf_ptr = sp.read_u64(3); - let out_buf_len = sp.read_u64(4); - const OUTPUT_ARG: usize = 6; - - let in_slice = read_slice(in_buf_ptr, in_buf_len); - let mut output = vec![0u8; out_buf_len as usize]; - let mut output_len = out_buf_len as usize; - let res = BrotliDecoderDecompress( - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BROTLI_RES_SUCCESS) || (output_len as u64 > out_buf_len) { - sp.write_u64(OUTPUT_ARG, u64::MAX); - return; - } - write_slice(&output[..output_len], out_buf_ptr); - sp.write_u64(OUTPUT_ARG, output_len as u64); - return; -} - -#[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbcompress_brotliCompress(sp: GoStack) { - //(inBuf []byte, outBuf []byte, level int, windowSize int) int - let in_buf_ptr = sp.read_u64(0); - let in_buf_len = sp.read_u64(1); - let out_buf_ptr = sp.read_u64(3); - let out_buf_len = sp.read_u64(4); - let level = sp.read_u64(6) as u32; - let windowsize = sp.read_u64(7) as u32; - const OUTPUT_ARG: usize = 8; - - let in_slice = read_slice(in_buf_ptr, in_buf_len); - let mut output = vec![0u8; out_buf_len as usize]; - let mut output_len = out_buf_len as usize; - let res = BrotliEncoderCompress( - level, - windowsize, - BROTLI_MODE_GENERIC, - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BROTLI_RES_SUCCESS) || (output_len as u64 > out_buf_len) { - sp.write_u64(OUTPUT_ARG, u64::MAX); - return; - } - write_slice(&output[..output_len], out_buf_ptr); - sp.write_u64(OUTPUT_ARG, output_len as u64); - return; -} diff --git a/arbitrator/wasm-libraries/forward/.gitignore b/arbitrator/wasm-libraries/forward/.gitignore new file mode 100644 index 000000000..40da2042b --- /dev/null +++ b/arbitrator/wasm-libraries/forward/.gitignore @@ -0,0 +1 @@ +**.wat diff --git a/arbitrator/wasm-libraries/forward/Cargo.toml b/arbitrator/wasm-libraries/forward/Cargo.toml new file mode 100644 index 000000000..73ed9d882 --- /dev/null +++ b/arbitrator/wasm-libraries/forward/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "forward" +version = "0.1.0" +edition = "2021" + +[dependencies] +eyre = "0.6.5" +structopt = "0.3.26" diff --git a/arbitrator/wasm-libraries/forward/src/main.rs b/arbitrator/wasm-libraries/forward/src/main.rs new file mode 100644 index 000000000..05a949e8a --- /dev/null +++ b/arbitrator/wasm-libraries/forward/src/main.rs @@ -0,0 +1,206 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use eyre::Result; +use std::{fs::File, io::Write, path::PathBuf}; +use structopt::StructOpt; + +/// order matters! +const HOSTIOS: [[&str; 3]; 42] = [ + ["read_args", "i32", ""], + ["write_result", "i32 i32", ""], + ["exit_early", "i32", ""], + ["storage_load_bytes32", "i32 i32", ""], + ["storage_cache_bytes32", "i32 i32", ""], + ["storage_flush_cache", "i32", ""], + ["transient_load_bytes32", "i32 i32", ""], + ["transient_store_bytes32", "i32 i32", ""], + ["call_contract", "i32 i32 i32 i32 i64 i32", "i32"], + ["delegate_call_contract", "i32 i32 i32 i64 i32", "i32"], + ["static_call_contract", "i32 i32 i32 i64 i32", "i32"], + ["create1", "i32 i32 i32 i32 i32", ""], + ["create2", "i32 i32 i32 i32 i32 i32", ""], + ["read_return_data", "i32 i32 i32", "i32"], + ["return_data_size", "", "i32"], + ["emit_log", "i32 i32 i32", ""], + ["account_balance", "i32 i32", ""], + ["account_code", "i32 i32 i32 i32", "i32"], + ["account_code_size", "i32", "i32"], + ["account_codehash", "i32 i32", ""], + ["evm_gas_left", "", "i64"], + ["evm_ink_left", "", "i64"], + ["block_basefee", "i32", ""], + ["chainid", "", "i64"], + ["block_coinbase", "i32", ""], + ["block_gas_limit", "", "i64"], + ["block_number", "", "i64"], + ["block_timestamp", "", "i64"], + ["contract_address", "i32", ""], + ["math_div", "i32 i32", ""], + ["math_mod", "i32 i32", ""], + ["math_pow", "i32 i32", ""], + ["math_add_mod", "i32 i32 i32", ""], + ["math_mul_mod", "i32 i32 i32", ""], + ["msg_reentrant", "", "i32"], + ["msg_sender", "i32", ""], + ["msg_value", "i32", ""], + ["native_keccak256", "i32 i32 i32", ""], + ["tx_gas_price", "i32", ""], + ["tx_ink_price", "", "i32"], + ["tx_origin", "i32", ""], + ["pay_for_memory_grow", "i32", ""], +]; + +#[derive(StructOpt)] +#[structopt(name = "arbitrator-prover")] +struct Opts { + #[structopt(long)] + path: PathBuf, + #[structopt(long)] + stub: bool, +} + +fn main() -> Result<()> { + let opts = Opts::from_args(); + let file = &mut File::options() + .create(true) + .write(true) + .truncate(true) + .open(opts.path)?; + + match opts.stub { + true => forward_stub(file), + false => forward(file), + } +} + +fn forward(file: &mut File) -> Result<()> { + macro_rules! wln { + ($($text:tt)*) => { + writeln!(file, $($text)*)?; + }; + } + let s = " "; + + wln!( + ";; Copyright 2022-2023, Offchain Labs, Inc.\n\ + ;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE\n\ + ;; This file is auto-generated.\n\ + \n\ + (module" + ); + + macro_rules! group { + ($list:expr, $kind:expr) => { + (!$list.is_empty()) + .then(|| format!(" ({} {})", $kind, $list)) + .unwrap_or_default() + }; + } + + wln!("{s};; symbols to re-export"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!( + r#"{s}(import "user_host" "arbitrator_forward__{name}" (func ${name}{params}{result}))"# + ); + } + wln!(); + + wln!("{s};; reserved offsets for future user_host imports"); + for i in HOSTIOS.len()..512 { + wln!("{s}(func $reserved_{i} unreachable)"); + } + wln!(); + + wln!( + "{s};; allows user_host to request a trap\n\ + {s}(global $trap (mut i32) (i32.const 0))\n\ + {s}(func $check\n\ + {s}{s}global.get $trap ;; see if set\n\ + {s}{s}(global.set $trap (i32.const 0)) ;; reset the flag\n\ + {s}{s}(if (then (unreachable)))\n\ + {s})\n\ + {s}(func (export \"forward__set_trap\")\n\ + {s}{s}(global.set $trap (i32.const 1))\n\ + {s})\n" + ); + + wln!("{s};; user linkage"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!("{s}(func (export \"vm_hooks__{name}\"){params}{result}"); + + let gets = (1 + ins.len()) / 4; + for i in 0..gets { + wln!("{s}{s}local.get {i}"); + } + + wln!( + "{s}{s}call ${name}\n\ + {s}{s}call $check\n\ + {s})" + ); + } + + wln!(")"); + Ok(()) +} + +fn forward_stub(file: &mut File) -> Result<()> { + macro_rules! wln { + ($($text:tt)*) => { + writeln!(file, $($text)*)?; + }; + } + let s = " "; + + wln!( + ";; Copyright 2022-2023, Offchain Labs, Inc.\n\ + ;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE\n\ + ;; This file is auto-generated.\n\ + \n\ + (module" + ); + + macro_rules! group { + ($list:expr, $kind:expr) => { + (!$list.is_empty()) + .then(|| format!(" ({} {})", $kind, $list)) + .unwrap_or_default() + }; + } + + wln!("{s};; stubs for the symbols we re-export"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!("{s}(func ${name}{params}{result} unreachable)"); + } + wln!(); + + wln!("{s};; reserved offsets for future user_host imports"); + for i in HOSTIOS.len()..512 { + wln!("{s}(func $reserved_{i} unreachable)"); + } + wln!(); + + wln!( + "{s};; allows user_host to request a trap\n\ + {s}(global $trap (mut i32) (i32.const 0))\n\ + {s}(func $check unreachable)\n\ + {s}(func (export \"forward__set_trap\") unreachable)" + ); + + wln!("{s};; user linkage"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!("{s}(func (export \"vm_hooks__{name}\"){params}{result} unreachable)"); + } + + wln!(")"); + Ok(()) +} diff --git a/arbitrator/wasm-libraries/go-abi/Cargo.toml b/arbitrator/wasm-libraries/go-abi/Cargo.toml deleted file mode 100644 index 36dc35c82..000000000 --- a/arbitrator/wasm-libraries/go-abi/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "go-abi" -version = "0.1.0" -edition = "2018" -publish = false - -[dependencies] diff --git a/arbitrator/wasm-libraries/go-abi/src/lib.rs b/arbitrator/wasm-libraries/go-abi/src/lib.rs deleted file mode 100644 index b6bcc45a6..000000000 --- a/arbitrator/wasm-libraries/go-abi/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::convert::TryFrom; - -extern "C" { - pub fn wavm_caller_load8(ptr: usize) -> u8; - pub fn wavm_caller_load32(ptr: usize) -> u32; - pub fn wavm_caller_store8(ptr: usize, val: u8); - pub fn wavm_caller_store32(ptr: usize, val: u32); - - pub fn wavm_guest_call__getsp() -> usize; - pub fn wavm_guest_call__resume(); -} - -pub unsafe fn wavm_caller_load64(ptr: usize) -> u64 { - let lower = wavm_caller_load32(ptr); - let upper = wavm_caller_load32(ptr + 4); - lower as u64 | ((upper as u64) << 32) -} - -pub unsafe fn wavm_caller_store64(ptr: usize, val: u64) { - wavm_caller_store32(ptr, val as u32); - wavm_caller_store32(ptr + 4, (val >> 32) as u32); -} - -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct GoStack(pub usize); - -impl GoStack { - fn offset(&self, arg: usize) -> usize { - self.0 + (arg + 1) * 8 - } - - pub unsafe fn read_u8(self, arg: usize) -> u8 { - wavm_caller_load8(self.offset(arg)) - } - - pub unsafe fn read_u32(self, arg: usize) -> u32 { - wavm_caller_load32(self.offset(arg)) - } - - pub unsafe fn read_u64(self, arg: usize) -> u64 { - wavm_caller_load64(self.offset(arg)) - } - - pub unsafe fn write_u8(self, arg: usize, x: u8) { - wavm_caller_store8(self.offset(arg), x); - } - - pub unsafe fn write_u32(self, arg: usize, x: u32) { - wavm_caller_store32(self.offset(arg), x); - } - - pub unsafe fn write_u64(self, arg: usize, x: u64) { - wavm_caller_store64(self.offset(arg), x); - } -} - -pub unsafe fn read_slice(ptr: u64, mut len: u64) -> Vec { - let mut data = Vec::with_capacity(len as usize); - if len == 0 { - return data; - } - let mut ptr = usize::try_from(ptr).expect("Go pointer didn't fit in usize"); - while len >= 4 { - data.extend(wavm_caller_load32(ptr).to_le_bytes()); - ptr += 4; - len -= 4; - } - for _ in 0..len { - data.push(wavm_caller_load8(ptr)); - ptr += 1; - } - data -} - -pub unsafe fn write_slice(mut src: &[u8], ptr: u64) { - if src.len() == 0 { - return; - } - let mut ptr = usize::try_from(ptr).expect("Go pointer didn't fit in usize"); - while src.len() >= 4 { - let mut arr = [0u8; 4]; - arr.copy_from_slice(&src[..4]); - wavm_caller_store32(ptr, u32::from_le_bytes(arr)); - ptr += 4; - src = &src[4..]; - } - for &byte in src { - wavm_caller_store8(ptr, byte); - ptr += 1; - } -} diff --git a/arbitrator/wasm-libraries/go-stub/Cargo.toml b/arbitrator/wasm-libraries/go-stub/Cargo.toml deleted file mode 100644 index 9398b2b44..000000000 --- a/arbitrator/wasm-libraries/go-stub/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "go-stub" -version = "0.1.0" -edition = "2018" -publish = false - -[lib] -crate-type = ["cdylib"] - -[dependencies] -fnv = "1.0.7" -rand = { version = "0.8.4", default-features = false } -rand_pcg = { version = "0.3.1", default-features = false } -go-abi = { path = "../go-abi" } - -[features] diff --git a/arbitrator/wasm-libraries/go-stub/src/lib.rs b/arbitrator/wasm-libraries/go-stub/src/lib.rs deleted file mode 100644 index 1a5d1963c..000000000 --- a/arbitrator/wasm-libraries/go-stub/src/lib.rs +++ /dev/null @@ -1,598 +0,0 @@ -mod value; - -use crate::value::*; -use fnv::FnvHashSet as HashSet; -use go_abi::*; -use rand::RngCore; -use rand_pcg::Pcg32; -use std::{collections::BinaryHeap, convert::TryFrom, io::Write}; - -fn interpret_value(repr: u64) -> InterpValue { - if repr == 0 { - return InterpValue::Undefined; - } - let float = f64::from_bits(repr); - if float.is_nan() && repr != f64::NAN.to_bits() { - let id = repr as u32; - if id == ZERO_ID { - return InterpValue::Number(0.); - } - return InterpValue::Ref(id); - } - InterpValue::Number(float) -} - -unsafe fn read_value_slice(mut ptr: u64, len: u64) -> Vec { - let mut values = Vec::new(); - for _ in 0..len { - let p = usize::try_from(ptr).expect("Go pointer didn't fit in usize"); - values.push(interpret_value(wavm_caller_load64(p))); - ptr += 8; - } - values -} - -#[no_mangle] -pub unsafe extern "C" fn go__debug(x: usize) { - println!("go debug: {}", x); -} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_resetMemoryDataView(_: GoStack) {} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_wasmExit(sp: GoStack) { - std::process::exit(sp.read_u32(0) as i32); -} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_wasmWrite(sp: GoStack) { - let fd = sp.read_u64(0); - let ptr = sp.read_u64(1); - let len = sp.read_u32(2); - let buf = read_slice(ptr, len.into()); - if fd == 2 { - let stderr = std::io::stderr(); - let mut stderr = stderr.lock(); - stderr.write_all(&buf).unwrap(); - } else { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - stdout.write_all(&buf).unwrap(); - } -} - -// An increasing clock used when Go asks for time, measured in nanoseconds. -static mut TIME: u64 = 0; -// The amount of TIME advanced each check. Currently 10 milliseconds. -static mut TIME_INTERVAL: u64 = 10_000_000; - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_nanotime1(sp: GoStack) { - TIME += TIME_INTERVAL; - sp.write_u64(0, TIME); -} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_walltime(sp: GoStack) { - TIME += TIME_INTERVAL; - sp.write_u64(0, TIME / 1_000_000_000); - sp.write_u32(1, (TIME % 1_000_000_000) as u32); -} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_walltime1(sp: GoStack) { - TIME += TIME_INTERVAL; - sp.write_u64(0, TIME / 1_000_000_000); - sp.write_u64(1, TIME % 1_000_000_000); -} - -static mut RNG: Option = None; - -unsafe fn get_rng<'a>() -> &'a mut Pcg32 { - RNG.get_or_insert_with(|| Pcg32::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7)) -} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_getRandomData(sp: GoStack) { - let rng = get_rng(); - let mut ptr = - usize::try_from(sp.read_u64(0)).expect("Go getRandomData pointer didn't fit in usize"); - let mut len = sp.read_u64(1); - while len >= 4 { - wavm_caller_store32(ptr, rng.next_u32()); - ptr += 4; - len -= 4; - } - if len > 0 { - let mut rem = rng.next_u32(); - for _ in 0..len { - wavm_caller_store8(ptr, rem as u8); - ptr += 1; - rem >>= 8; - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct TimeoutInfo { - time: u64, - id: u32, -} - -impl Ord for TimeoutInfo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - other - .time - .cmp(&self.time) - .then_with(|| other.id.cmp(&self.id)) - } -} - -impl PartialOrd for TimeoutInfo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) - } -} - -#[derive(Default, Debug)] -struct TimeoutState { - /// Contains tuples of (time, id) - times: BinaryHeap, - pending_ids: HashSet, - next_id: u32, -} - -static mut TIMEOUT_STATE: Option = None; - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_scheduleTimeoutEvent(sp: GoStack) { - let mut time = sp.read_u64(0); - time = time.saturating_mul(1_000_000); // milliseconds to nanoseconds - time = time.saturating_add(TIME); // add the current time to the delay - - let state = TIMEOUT_STATE.get_or_insert_with(Default::default); - let id = state.next_id; - state.next_id += 1; - state.times.push(TimeoutInfo { time, id }); - state.pending_ids.insert(id); - - sp.write_u32(1, id); -} - -#[no_mangle] -pub unsafe extern "C" fn go__runtime_clearTimeoutEvent(sp: GoStack) { - let id = sp.read_u32(0); - - let state = TIMEOUT_STATE.get_or_insert_with(Default::default); - if !state.pending_ids.remove(&id) { - eprintln!("Go attempting to clear not pending timeout event {}", id); - } -} - -macro_rules! unimpl_js { - ($($f:ident),* $(,)?) => { - $( - #[no_mangle] - pub unsafe extern "C" fn $f(_: GoStack) { - unimplemented!("Go JS interface {} not supported", stringify!($f)); - } - )* - } -} - -unimpl_js!( - go__syscall_js_stringVal, - go__syscall_js_valueSetIndex, - go__syscall_js_valuePrepareString, - go__syscall_js_valueLoadString, - go__syscall_js_valueDelete, - go__syscall_js_valueInvoke, - go__syscall_js_valueInstanceOf, -); - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_valueGet(sp: GoStack) { - let source = interpret_value(sp.read_u64(0)); - let field_ptr = sp.read_u64(1); - let field_len = sp.read_u64(2); - let field = read_slice(field_ptr, field_len); - let value = match source { - InterpValue::Ref(id) => get_field(id, &field), - val => { - eprintln!( - "Go attempting to read field {:?} . {}", - val, - String::from_utf8_lossy(&field), - ); - GoValue::Null - } - }; - sp.write_u64(3, value.encode()); -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_valueNew(sp: GoStack) { - let class = sp.read_u32(0); - let args_ptr = sp.read_u64(1); - let args_len = sp.read_u64(2); - let args = read_value_slice(args_ptr, args_len); - if class == UINT8_ARRAY_ID { - if let Some(InterpValue::Number(size)) = args.first() { - let id = DynamicObjectPool::singleton() - .insert(DynamicObject::Uint8Array(vec![0; *size as usize])); - sp.write_u64(4, GoValue::Object(id).encode()); - sp.write_u8(5, 1); - return; - } else { - eprintln!( - "Go attempted to construct Uint8Array with bad args: {:?}", - args, - ); - } - } else if class == DATE_ID { - let id = DynamicObjectPool::singleton().insert(DynamicObject::Date); - sp.write_u64(4, GoValue::Object(id).encode()); - sp.write_u8(5, 1); - return; - } else { - eprintln!( - "Go attempting to construct unimplemented JS value {}", - class, - ); - } - sp.write_u64(4, GoValue::Null.encode()); - sp.write_u8(5, 0); -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_copyBytesToJS(sp: GoStack) { - let dest_val = interpret_value(sp.read_u64(0)); - if let InterpValue::Ref(dest_id) = dest_val { - let src_ptr = sp.read_u64(1); - let src_len = sp.read_u64(2); - let dest = DynamicObjectPool::singleton().get_mut(dest_id); - if let Some(DynamicObject::Uint8Array(buf)) = dest { - if buf.len() as u64 != src_len { - eprintln!( - "Go copying bytes from Go source length {} to JS dest length {}", - src_len, - buf.len(), - ); - } - let len = std::cmp::min(src_len, buf.len() as u64) as usize; - // Slightly inefficient as this allocates a new temporary buffer - buf[..len].copy_from_slice(&read_slice(src_ptr, len as u64)); - sp.write_u64(4, GoValue::Number(len as f64).encode()); - sp.write_u8(5, 1); - return; - } else { - eprintln!( - "Go attempting to copy bytes into unsupported target {:?}", - dest, - ); - } - } else { - eprintln!("Go attempting to copy bytes into {:?}", dest_val); - } - sp.write_u64(4, GoValue::Null.encode()); - sp.write_u8(5, 0); -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_copyBytesToGo(sp: GoStack) { - let dest_ptr = sp.read_u64(0); - let dest_len = sp.read_u64(1); - let src_val = interpret_value(sp.read_u64(3)); - if let InterpValue::Ref(src_id) = src_val { - let source = DynamicObjectPool::singleton().get_mut(src_id); - if let Some(DynamicObject::Uint8Array(buf)) = source { - if buf.len() as u64 != dest_len { - eprintln!( - "Go copying bytes from JS source length {} to Go dest length {}", - buf.len(), - dest_len, - ); - } - let len = std::cmp::min(buf.len() as u64, dest_len) as usize; - write_slice(&buf[..len], dest_ptr); - - sp.write_u64(4, GoValue::Number(len as f64).encode()); - sp.write_u8(5, 1); - return; - } else { - eprintln!( - "Go attempting to copy bytes from unsupported source {:?}", - source, - ); - } - } else { - eprintln!("Go attempting to copy bytes from {:?}", src_val); - } - sp.write_u8(5, 0); -} - -unsafe fn value_call_impl(sp: &mut GoStack) -> Result { - let object = interpret_value(sp.read_u64(0)); - let method_name_ptr = sp.read_u64(1); - let method_name_len = sp.read_u64(2); - let method_name = read_slice(method_name_ptr, method_name_len); - let args_ptr = sp.read_u64(3); - let args_len = sp.read_u64(4); - let args = read_value_slice(args_ptr, args_len); - if object == InterpValue::Ref(GO_ID) && &method_name == b"_makeFuncWrapper" { - let id = args.first().ok_or_else(|| { - format!( - "Go attempting to call Go._makeFuncWrapper with bad args {:?}", - args, - ) - })?; - let ref_id = - DynamicObjectPool::singleton().insert(DynamicObject::FunctionWrapper(*id, object)); - Ok(GoValue::Function(ref_id)) - } else if object == InterpValue::Ref(FS_ID) && &method_name == b"write" { - let args_len = std::cmp::min(6, args.len()); - if let &[InterpValue::Number(fd), InterpValue::Ref(buf_id), InterpValue::Number(offset), InterpValue::Number(length), InterpValue::Ref(NULL_ID), InterpValue::Ref(callback_id)] = - &args.as_slice()[..args_len] - { - let object_pool = DynamicObjectPool::singleton(); - let buf = match object_pool.get(buf_id) { - Some(DynamicObject::Uint8Array(x)) => x, - x => { - return Err(format!( - "Go attempting to call fs.write with bad buffer {:?}", - x, - )) - } - }; - let (func_id, this) = match object_pool.get(callback_id) { - Some(DynamicObject::FunctionWrapper(f, t)) => (f, t), - x => { - return Err(format!( - "Go attempting to call fs.write with bad buffer {:?}", - x, - )) - } - }; - let mut offset = offset as usize; - let mut length = length as usize; - if offset > buf.len() { - eprintln!( - "Go attempting to call fs.write with offset {} >= buf.len() {}", - offset, - buf.len(), - ); - offset = buf.len(); - } - if offset + length > buf.len() { - eprintln!( - "Go attempting to call fs.write with offset {} + length {} >= buf.len() {}", - offset, - length, - buf.len(), - ); - length = buf.len() - offset; - } - - if fd == 1. { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - stdout.write_all(&buf[offset..(offset + length)]).unwrap(); - } else if fd == 2. { - let stderr = std::io::stderr(); - let mut stderr = stderr.lock(); - stderr.write_all(&buf[offset..(offset + length)]).unwrap(); - } else { - eprintln!("Go attempting to write to unknown FD {}", fd); - } - - PENDING_EVENT = Some(PendingEvent { - id: *func_id, - this: *this, - args: vec![ - GoValue::Null, // no error - GoValue::Number(length as f64), // amount written - ], - }); - wavm_guest_call__resume(); - - *sp = GoStack(wavm_guest_call__getsp()); - Ok(GoValue::Null) - } else { - Err(format!( - "Go attempting to call fs.write with bad args {:?}", - args - )) - } - } else if object == InterpValue::Ref(CRYPTO_ID) && &method_name == b"getRandomValues" { - let id = match args.first() { - Some(InterpValue::Ref(x)) => *x, - _ => { - return Err(format!( - "Go attempting to call crypto.getRandomValues with bad args {:?}", - args, - )); - } - }; - match DynamicObjectPool::singleton().get_mut(id) { - Some(DynamicObject::Uint8Array(buf)) => { - get_rng().fill_bytes(buf.as_mut_slice()); - } - Some(x) => { - return Err(format!( - "Go attempting to call crypto.getRandomValues on bad object {:?}", - x, - )); - } - None => { - return Err(format!( - "Go attempting to call crypto.getRandomValues on unknown reference {}", - id, - )); - } - } - Ok(GoValue::Undefined) - } else if let InterpValue::Ref(obj_id) = object { - let val = DynamicObjectPool::singleton().get(obj_id); - if let Some(DynamicObject::Date) = val { - if &method_name == b"getTimezoneOffset" { - return Ok(GoValue::Number(0.0)); - } else { - return Err(format!( - "Go attempting to call unknown method {} for date object", - String::from_utf8_lossy(&method_name), - )); - } - } else { - return Err(format!( - "Go attempting to call method {} for unknown object - id {}", - String::from_utf8_lossy(&method_name), - obj_id, - )); - } - } else { - Err(format!( - "Go attempting to call unknown method {:?} . {}", - object, - String::from_utf8_lossy(&method_name), - )) - } -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_valueCall(mut sp: GoStack) { - match value_call_impl(&mut sp) { - Ok(val) => { - sp.write_u64(6, val.encode()); - sp.write_u8(7, 1); - } - Err(err) => { - eprintln!("{}", err); - sp.write_u64(6, GoValue::Null.encode()); - sp.write_u8(7, 0); - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_valueSet(sp: GoStack) { - let source = interpret_value(sp.read_u64(0)); - let field_ptr = sp.read_u64(1); - let field_len = sp.read_u64(2); - let new_value = interpret_value(sp.read_u64(3)); - let field = read_slice(field_ptr, field_len); - if source == InterpValue::Ref(GO_ID) - && &field == b"_pendingEvent" - && new_value == InterpValue::Ref(NULL_ID) - { - PENDING_EVENT = None; - return; - } - let pool = DynamicObjectPool::singleton(); - if let InterpValue::Ref(id) = source { - let source = pool.get(id); - if let Some(DynamicObject::PendingEvent(_)) = source { - if field == b"result" { - return; - } - } - } - eprintln!( - "Go attempted to set unsupported value {:?} field {} to {:?}", - source, - String::from_utf8_lossy(&field), - new_value, - ); -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_valueLength(sp: GoStack) { - let source = interpret_value(sp.read_u64(0)); - let pool = DynamicObjectPool::singleton(); - let source = match source { - InterpValue::Ref(x) => pool.get(x), - _ => None, - }; - let len = match source { - Some(DynamicObject::Uint8Array(x)) => Some(x.len()), - Some(DynamicObject::ValueArray(x)) => Some(x.len()), - _ => None, - }; - if let Some(len) = len { - sp.write_u64(1, len as u64); - } else { - eprintln!( - "Go attempted to get length of unsupported value {:?}", - source, - ); - sp.write_u64(1, 0); - } -} - -unsafe fn value_index_impl(sp: GoStack) -> Result { - let pool = DynamicObjectPool::singleton(); - let source = match interpret_value(sp.read_u64(0)) { - InterpValue::Ref(x) => pool.get(x), - val => return Err(format!("Go attempted to index into {:?}", val)), - }; - let index = usize::try_from(sp.read_u64(1)).map_err(|e| format!("{:?}", e))?; - let val = match source { - Some(DynamicObject::Uint8Array(x)) => { - Some(x.get(index).map(|x| GoValue::Number(*x as f64))) - } - Some(DynamicObject::ValueArray(x)) => Some(x.get(index).cloned()), - _ => None, - }; - match val { - Some(Some(val)) => Ok(val), - Some(None) => Err(format!( - "Go attempted to index out of bounds into value {:?} index {}", - source, index, - )), - None => Err(format!( - "Go attempted to index into unsupported value {:?}", - source - )), - } -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_valueIndex(sp: GoStack) { - match value_index_impl(sp) { - Ok(v) => sp.write_u64(2, v.encode()), - Err(e) => { - eprintln!("{}", e); - sp.write_u64(2, GoValue::Null.encode()); - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn go__syscall_js_finalizeRef(sp: GoStack) { - let val = interpret_value(sp.read_u64(0)); - match val { - InterpValue::Ref(x) if x < DYNAMIC_OBJECT_ID_BASE => {} - InterpValue::Ref(x) => { - if DynamicObjectPool::singleton().remove(x).is_none() { - eprintln!("Go attempting to finalize unknown ref {}", x); - } - } - val => eprintln!("Go attempting to finalize {:?}", val), - } -} - -#[no_mangle] -pub unsafe extern "C" fn wavm__go_after_run() { - let mut state = TIMEOUT_STATE.get_or_insert_with(Default::default); - while let Some(info) = state.times.pop() { - while state.pending_ids.contains(&info.id) { - TIME = std::cmp::max(TIME, info.time); - // Important: the current reference to state shouldn't be used after this resume call, - // as it might during the resume call the reference might be invalidated. - // That's why immediately after this resume call, we replace the reference - // with a new reference to TIMEOUT_STATE. - wavm_guest_call__resume(); - state = TIMEOUT_STATE.get_or_insert_with(Default::default); - } - } -} diff --git a/arbitrator/wasm-libraries/host-io/Cargo.toml b/arbitrator/wasm-libraries/host-io/Cargo.toml index 48f498f91..03803400c 100644 --- a/arbitrator/wasm-libraries/host-io/Cargo.toml +++ b/arbitrator/wasm-libraries/host-io/Cargo.toml @@ -8,5 +8,5 @@ publish = false crate-type = ["cdylib"] [dependencies] -go-abi = { path = "../go-abi" } -arbutil = { path = "../../arbutil" } +arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/", default-features = false, features = ["static_caller"] } diff --git a/arbitrator/wasm-libraries/host-io/src/lib.rs b/arbitrator/wasm-libraries/host-io/src/lib.rs index 824678e75..1f1935e38 100644 --- a/arbitrator/wasm-libraries/host-io/src/lib.rs +++ b/arbitrator/wasm-libraries/host-io/src/lib.rs @@ -1,6 +1,12 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::missing_safety_doc)] // TODO: add safety docs + use arbutil::PreimageType; -use go_abi::*; -use std::convert::TryInto; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use core::convert::TryInto; +use core::ops::{Deref, DerefMut, Index, RangeTo}; extern "C" { pub fn wavm_get_globalstate_bytes32(idx: u32, ptr: *mut u8); @@ -18,145 +24,124 @@ extern "C" { #[repr(C, align(256))] struct MemoryLeaf([u8; 32]); -#[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_getGlobalStateBytes32( - sp: GoStack, -) { - let idx = sp.read_u64(0) as u32; - let out_ptr = sp.read_u64(1); - let mut out_len = sp.read_u64(2); - if out_len < 32 { - eprintln!( - "Go attempting to read block hash into {} bytes long buffer", - out_len, - ); - } else { - out_len = 32; +impl Deref for MemoryLeaf { + type Target = [u8; 32]; + + fn deref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl DerefMut for MemoryLeaf { + fn deref_mut(&mut self) -> &mut [u8; 32] { + &mut self.0 } +} + +impl Index> for MemoryLeaf { + type Output = [u8]; + + fn index(&self, index: RangeTo) -> &[u8] { + &self.0[index] + } +} + +#[no_mangle] +pub unsafe extern "C" fn wavmio__getGlobalStateBytes32(idx: u32, out_ptr: GuestPtr) { let mut our_buf = MemoryLeaf([0u8; 32]); - let our_ptr = our_buf.0.as_mut_ptr(); + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); wavm_get_globalstate_bytes32(idx, our_ptr); - write_slice(&our_buf.0[..(out_len as usize)], out_ptr); + STATIC_MEM.write_slice(out_ptr, &our_buf[..32]); } +/// Writes 32-bytes of global state #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_setGlobalStateBytes32( - sp: GoStack, -) { - let idx = sp.read_u64(0) as u32; - let src_ptr = sp.read_u64(1); - let src_len = sp.read_u64(2); - if src_len != 32 { - eprintln!( - "Go attempting to set block hash from {} bytes long buffer", - src_len, - ); - return; - } +pub unsafe extern "C" fn wavmio__setGlobalStateBytes32(idx: u32, src_ptr: GuestPtr) { let mut our_buf = MemoryLeaf([0u8; 32]); - our_buf.0.copy_from_slice(&read_slice(src_ptr, src_len)); - let our_ptr = our_buf.0.as_ptr(); + let value = STATIC_MEM.read_slice(src_ptr, 32); + our_buf.copy_from_slice(&value); + + let our_ptr = our_buf.as_ptr(); assert_eq!(our_ptr as usize % 32, 0); wavm_set_globalstate_bytes32(idx, our_ptr); } +/// Reads 8-bytes of global state #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_getGlobalStateU64(sp: GoStack) { - let idx = sp.read_u64(0) as u32; - sp.write_u64(1, wavm_get_globalstate_u64(idx)); +pub unsafe extern "C" fn wavmio__getGlobalStateU64(idx: u32) -> u64 { + wavm_get_globalstate_u64(idx) } +/// Writes 8-bytes of global state #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_setGlobalStateU64(sp: GoStack) { - let idx = sp.read_u64(0) as u32; - wavm_set_globalstate_u64(idx, sp.read_u64(1)); +pub unsafe extern "C" fn wavmio__setGlobalStateU64(idx: u32, val: u64) { + wavm_set_globalstate_u64(idx, val); } +/// Reads an inbox message #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_readInboxMessage(sp: GoStack) { - let msg_num = sp.read_u64(0); - let offset = sp.read_u64(1); - let out_ptr = sp.read_u64(2); - let out_len = sp.read_u64(3); - if out_len != 32 { - eprintln!( - "Go attempting to read inbox message with out len {}", - out_len, - ); - sp.write_u64(5, 0); - return; - } +pub unsafe extern "C" fn wavmio__readInboxMessage( + msg_num: u64, + offset: usize, + out_ptr: GuestPtr, +) -> usize { let mut our_buf = MemoryLeaf([0u8; 32]); - let our_ptr = our_buf.0.as_mut_ptr(); + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); - let read = wavm_read_inbox_message(msg_num, our_ptr, offset as usize); + + let read = wavm_read_inbox_message(msg_num, our_ptr, offset); assert!(read <= 32); - write_slice(&our_buf.0[..read], out_ptr); - sp.write_u64(5, read as u64); + STATIC_MEM.write_slice(out_ptr, &our_buf[..read]); + read } +/// Reads a delayed inbox message #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_readDelayedInboxMessage( - sp: GoStack, -) { - let seq_num = sp.read_u64(0); - let offset = sp.read_u64(1); - let out_ptr = sp.read_u64(2); - let out_len = sp.read_u64(3); - if out_len != 32 { - eprintln!( - "Go attempting to read inbox message with out len {}", - out_len, - ); - sp.write_u64(4, 0); - return; - } +pub unsafe extern "C" fn wavmio__readDelayedInboxMessage( + msg_num: u64, + offset: usize, + out_ptr: GuestPtr, +) -> usize { let mut our_buf = MemoryLeaf([0u8; 32]); - let our_ptr = our_buf.0.as_mut_ptr(); + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); - let read = wavm_read_delayed_inbox_message(seq_num, our_ptr, offset as usize); + + let read = wavm_read_delayed_inbox_message(msg_num, our_ptr, offset); assert!(read <= 32); - write_slice(&our_buf.0[..read], out_ptr); - sp.write_u64(5, read as u64); + STATIC_MEM.write_slice(out_ptr, &our_buf[..read]); + read } +/// Retrieves the preimage of the given hash. #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_wavmio_resolveTypedPreimage(sp: GoStack) { - let preimage_type = sp.read_u8(0); - let hash_ptr = sp.read_u64(1); - let hash_len = sp.read_u64(2); - let offset = sp.read_u64(4); - let out_ptr = sp.read_u64(5); - let out_len = sp.read_u64(6); - if hash_len != 32 || out_len != 32 { - eprintln!( - "Go attempting to resolve preimage with hash len {} and out len {}", - hash_len, out_len, - ); - sp.write_u64(8, 0); - return; - } - let Ok(preimage_type) = preimage_type.try_into() else { - eprintln!( - "Go trying to resolve preimage with unknown type {}", - preimage_type - ); - sp.write_u64(8, 0); - return; - }; +pub unsafe extern "C" fn wavmio__resolveTypedPreimage( + preimage_type: u8, + hash_ptr: GuestPtr, + offset: usize, + out_ptr: GuestPtr, +) -> usize { + let mut our_buf = MemoryLeaf([0u8; 32]); + let hash = STATIC_MEM.read_slice(hash_ptr, 32); + our_buf.copy_from_slice(&hash); + + let our_ptr = our_buf.as_mut_ptr(); + assert_eq!(our_ptr as usize % 32, 0); let mut our_buf = MemoryLeaf([0u8; 32]); - our_buf.0.copy_from_slice(&read_slice(hash_ptr, hash_len)); - let our_ptr = our_buf.0.as_mut_ptr(); + let hash = STATIC_MEM.read_slice(hash_ptr, 32); + our_buf.copy_from_slice(&hash); + + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); + let preimage_type: PreimageType = preimage_type.try_into().expect("unsupported preimage type"); let preimage_reader = match preimage_type { PreimageType::Keccak256 => wavm_read_keccak_256_preimage, PreimageType::Sha2_256 => wavm_read_sha2_256_preimage, PreimageType::EthVersionedHash => wavm_read_eth_versioned_hash_preimage, PreimageType::EigenDAHash => wavm_read_eigen_da_hash_preimage, }; - let read = preimage_reader(our_ptr, offset as usize); + let read = preimage_reader(our_ptr, offset); assert!(read <= 32); - write_slice(&our_buf.0[..read], out_ptr); - sp.write_u64(8, read as u64); + STATIC_MEM.write_slice(out_ptr, &our_buf[..read]); + read } diff --git a/arbitrator/wasm-libraries/brotli/Cargo.toml b/arbitrator/wasm-libraries/program-exec/Cargo.toml similarity index 58% rename from arbitrator/wasm-libraries/brotli/Cargo.toml rename to arbitrator/wasm-libraries/program-exec/Cargo.toml index 304fc4c4e..d45f5fe61 100644 --- a/arbitrator/wasm-libraries/brotli/Cargo.toml +++ b/arbitrator/wasm-libraries/program-exec/Cargo.toml @@ -1,11 +1,9 @@ [package] -name = "brotli" +name = "program-exec" version = "0.1.0" edition = "2021" -publish = false [lib] crate-type = ["cdylib"] [dependencies] -go-abi = { path = "../go-abi" } diff --git a/arbitrator/wasm-libraries/program-exec/src/lib.rs b/arbitrator/wasm-libraries/program-exec/src/lib.rs new file mode 100644 index 000000000..841da1349 --- /dev/null +++ b/arbitrator/wasm-libraries/program-exec/src/lib.rs @@ -0,0 +1,58 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#[link(wasm_import_module = "hostio")] +extern "C" { + fn program_continue(response: u32) -> u32; + fn program_call_main(module: u32, args_len: usize) -> u32; +} + +#[link(wasm_import_module = "program_internal")] +extern "C" { + fn set_done(status: u32) -> u32; + fn args_len(module: u32) -> usize; +} + +// This module works with user-host +// It has the calls from the main (go) module which transfer +// control to a cothread. +// +// In any time, user-host module's stack may have multiple +// co-threads waiting inside it, due to co-threads making +// to launch a new stylus program (=new cothread). This is +// o.k. because these thread calls are FIFO. +// the main go-module is not FIFO - i.e. we return to go +// while a cothread is waiting for a response - so +// all go-calls come here + +// request_ids start above 0x100 +// return status are 1 byte, so they don't mix +// if we got a return status - notify user-host +// user-host will generate an "execution done" request +fn check_program_done(mut req_id: u32) -> u32 { + if req_id < 0x100 { + unsafe { + req_id = set_done(req_id); + } + } + req_id +} + +/// starts the program (in jit waits for first request) +/// module MUST match last module number returned from new_program +/// returns request_id for the first request from the program +#[no_mangle] +pub unsafe extern "C" fn programs__start_program(module: u32) -> u32 { + // call the program + let args_len = args_len(module); + check_program_done(program_call_main(module, args_len)) +} + +// sends previous response and transfers control to program +// MUST be called right after set_response to the same id +// returns request_id for the next request +#[no_mangle] +pub unsafe extern "C" fn programs__send_response(req_id: u32) -> u32 { + // call the program + check_program_done(program_continue(req_id)) +} diff --git a/arbitrator/wasm-libraries/user-host-trait/Cargo.toml b/arbitrator/wasm-libraries/user-host-trait/Cargo.toml new file mode 100644 index 000000000..95357f849 --- /dev/null +++ b/arbitrator/wasm-libraries/user-host-trait/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "user-host-trait" +version = "0.1.0" +edition = "2021" + +[dependencies] +arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/" } +prover = { path = "../../prover/", default-features = false } +eyre = "0.6.5" +ruint2 = "1.9.0" diff --git a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs new file mode 100644 index 000000000..0191718dc --- /dev/null +++ b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs @@ -0,0 +1,952 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use arbutil::{ + crypto, + evm::{ + self, + api::{DataReader, EvmApi}, + storage::StorageCache, + user::UserOutcomeKind, + EvmData, + }, + pricing::{self, EVM_API_INK, HOSTIO_INK, PTR_INK}, + Bytes20, Bytes32, +}; +pub use caller_env::GuestPtr; +use eyre::{eyre, Result}; +use prover::{ + programs::{meter::OutOfInkError, prelude::*}, + value::Value, +}; +use ruint2::Uint; +use std::fmt::Display; + +macro_rules! be { + ($int:expr) => { + $int.to_be_bytes() + }; +} + +macro_rules! trace { + ($name:expr, $env:expr, [$($args:expr),+], [$($outs:expr),+], $ret:expr) => {{ + if $env.evm_data().tracing { + let end_ink = $env.ink_ready()?; + let mut args = vec![]; + $(args.extend($args);)* + let mut outs = vec![]; + $(outs.extend($outs);)* + $env.trace($name, &args, &outs, end_ink); + } + Ok($ret) + }}; + ($name:expr, $env:expr, [$($args:expr),+], $outs:expr) => {{ + trace!($name, $env, [$($args),+], $outs, ()) + }}; + ($name:expr, $env:expr, $args:expr, $outs:expr) => {{ + trace!($name, $env, $args, $outs, ()) + }}; + ($name:expr, $env:expr, [$($args:expr),+], $outs:expr, $ret:expr) => { + trace!($name, $env, [$($args),+], [$outs], $ret) + }; + ($name:expr, $env:expr, $args:expr, $outs:expr, $ret:expr) => { + trace!($name, $env, [$args], [$outs], $ret) + }; +} +type Address = Bytes20; +type Wei = Bytes32; +type U256 = Uint<256, 4>; + +#[allow(clippy::too_many_arguments)] +pub trait UserHost: GasMeteredMachine { + type Err: From + From + From; + type MemoryErr; + type A: EvmApi; + + fn args(&self) -> &[u8]; + fn outs(&mut self) -> &mut Vec; + + fn evm_api(&mut self) -> &mut Self::A; + fn evm_data(&self) -> &EvmData; + fn evm_return_data_len(&mut self) -> &mut u32; + + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, Self::MemoryErr>; + fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], Self::MemoryErr>; + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), Self::MemoryErr>; + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), Self::MemoryErr>; + + fn read_bytes20(&self, ptr: GuestPtr) -> Result { + self.read_fixed(ptr).map(Into::into) + } + fn read_bytes32(&self, ptr: GuestPtr) -> Result { + self.read_fixed(ptr).map(Into::into) + } + fn read_u256(&self, ptr: GuestPtr) -> Result<(U256, Bytes32), Self::MemoryErr> { + let value = self.read_bytes32(ptr)?; + Ok((value.into(), value)) + } + + fn say(&self, text: D); + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64); + + fn write_bytes20(&self, ptr: GuestPtr, src: Bytes20) -> Result<(), Self::MemoryErr> { + self.write_slice(ptr, &src.0) + } + fn write_bytes32(&self, ptr: GuestPtr, src: Bytes32) -> Result<(), Self::MemoryErr> { + self.write_slice(ptr, &src.0) + } + + /// Reads the program calldata. The semantics are equivalent to that of the EVM's + /// [`CALLDATA_COPY`] opcode when requesting the entirety of the current call's calldata. + /// + /// [`CALLDATA_COPY`]: https://www.evm.codes/#37 + fn read_args(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK)?; + self.pay_for_write(self.args().len() as u32)?; + self.write_slice(ptr, self.args())?; + trace!("read_args", self, &[], self.args()) + } + + /// Writes the final return data. If not called before the program exists, the return data will + /// be 0 bytes long. Note that this hostio does not cause the program to exit, which happens + /// naturally when `user_entrypoint` returns. + fn write_result(&mut self, ptr: GuestPtr, len: u32) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK)?; + self.pay_for_read(len)?; + self.pay_for_geth_bytes(len)?; // returned after call + *self.outs() = self.read_slice(ptr, len)?; + trace!("write_result", self, &*self.outs(), &[]) + } + + /// Exits program execution early with the given status code. + /// If `0`, the program returns successfully with any data supplied by `write_result`. + /// Otherwise, the program reverts and treats any `write_result` data as revert data. + /// + /// The semantics are equivalent to that of the EVM's [`Return`] and [`Revert`] opcodes. + /// Note: this function just traces, it's up to the caller to actually perform the exit. + /// + /// [`Return`]: https://www.evm.codes/#f3 + /// [`Revert`]: https://www.evm.codes/#fd + fn exit_early(&mut self, status: u32) -> Result<(), Self::Err> { + trace!("exit_early", self, be!(status), &[]) + } + + /// Reads a 32-byte value from permanent storage. Stylus's storage format is identical to + /// that of the EVM. This means that, under the hood, this hostio is accessing the 32-byte + /// value stored in the EVM state trie at offset `key`, which will be `0` when not previously + /// set. The semantics, then, are equivalent to that of the EVM's [`SLOAD`] opcode. + /// + /// Note: the Stylus VM implements storage caching. This means that repeated calls to the same key + /// will cost less than in the EVM. + /// + /// [`SLOAD`]: https://www.evm.codes/#54 + fn storage_load_bytes32(&mut self, key: GuestPtr, dest: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK)?; + self.require_gas(evm::COLD_SLOAD_GAS + EVM_API_INK + StorageCache::REQUIRED_ACCESS_GAS)?; // cache-miss case + + let key = self.read_bytes32(key)?; + + let (value, gas_cost) = self.evm_api().get_bytes32(key); + self.buy_gas(gas_cost)?; + self.write_bytes32(dest, value)?; + trace!("storage_load_bytes32", self, key, value) + } + + /// Writes a 32-byte value to the permanent storage cache. Stylus's storage format is identical to that + /// of the EVM. This means that, under the hood, this hostio represents storing a 32-byte value into + /// the EVM state trie at offset `key`. Refunds are tabulated exactly as in the EVM. The semantics, then, + /// are equivalent to that of the EVM's [`SSTORE`] opcode. + /// + /// Note: because this value is cached, one must call `storage_flush_cache` to persist the value. + /// + /// Auditor's note: we require the [`SSTORE`] sentry per EVM rules. The `gas_cost` returned by the EVM API + /// may exceed this amount, but that's ok because the predominant cost is due to state bloat concerns. + /// + /// [`SSTORE`]: https://www.evm.codes/#55 + fn storage_cache_bytes32(&mut self, key: GuestPtr, value: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK)?; + self.require_gas(evm::SSTORE_SENTRY_GAS + StorageCache::REQUIRED_ACCESS_GAS)?; // see operations_acl_arbitrum.go + + let key = self.read_bytes32(key)?; + let value = self.read_bytes32(value)?; + + let gas_cost = self.evm_api().cache_bytes32(key, value); + self.buy_gas(gas_cost)?; + trace!("storage_cache_bytes32", self, [key, value], &[]) + } + + /// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested. + /// Analogous to repeated invocations of [`SSTORE`]. + /// + /// [`SSTORE`]: https://www.evm.codes/#55 + fn storage_flush_cache(&mut self, clear: bool) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + self.require_gas(evm::SSTORE_SENTRY_GAS)?; // see operations_acl_arbitrum.go + + let gas_left = self.gas_left()?; + self.evm_api().flush_storage_cache(clear, gas_left)?; + trace!("storage_flush_cache", self, [be!(clear as u8)], &[]) + } + + /// Reads a 32-byte value from transient storage. Stylus's storage format is identical to + /// that of the EVM. This means that, under the hood, this hostio is accessing the 32-byte + /// value stored in the EVM's transient state trie at offset `key`, which will be `0` when not previously + /// set. The semantics, then, are equivalent to that of the EVM's [`TLOAD`] opcode. + /// + /// [`TLOAD`]: https://www.evm.codes/#5c + fn transient_load_bytes32(&mut self, key: GuestPtr, dest: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; + self.buy_gas(evm::TLOAD_GAS)?; + + let key = self.read_bytes32(key)?; + let value = self.evm_api().get_transient_bytes32(key); + self.write_bytes32(dest, value)?; + trace!("transient_load_bytes32", self, key, value) + } + + /// Writes a 32-byte value to transient storage. Stylus's storage format is identical to that + /// of the EVM. This means that, under the hood, this hostio represents storing a 32-byte value into + /// the EVM's transient state trie at offset `key`. The semantics, then, are equivalent to that of the + /// EVM's [`TSTORE`] opcode. + /// + /// [`TSTORE`]: https://www.evm.codes/#5d + fn transient_store_bytes32(&mut self, key: GuestPtr, value: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; + self.buy_gas(evm::TSTORE_GAS)?; + + let key = self.read_bytes32(key)?; + let value = self.read_bytes32(value)?; + self.evm_api().set_transient_bytes32(key, value)?; + trace!("transient_store_bytes32", self, [key, value], &[]) + } + + /// Calls the contract at the given address with options for passing value and to limit the + /// amount of gas supplied. The return status indicates whether the call succeeded, and is + /// nonzero on failure. + /// + /// In both cases `return_data_len` will store the length of the result, the bytes of which can + /// be read via the `read_return_data` hostio. The bytes are not returned directly so that the + /// programmer can potentially save gas by choosing which subset of the return result they'd + /// like to copy. + /// + /// The semantics are equivalent to that of the EVM's [`CALL`] opcode, including callvalue + /// stipends and the 63/64 gas rule. This means that supplying the `u64::MAX` gas can be used + /// to send as much as possible. + /// + /// [`CALL`]: https://www.evm.codes/#f1 + fn call_contract( + &mut self, + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + value: GuestPtr, + gas: u64, + ret_len: GuestPtr, + ) -> Result { + let value = Some(value); + let call = |api: &mut Self::A, contract, data: &_, left, req, value: Option<_>| { + api.contract_call(contract, data, left, req, value.unwrap()) + }; + self.do_call(contract, data, data_len, value, gas, ret_len, call, "") + } + + /// Delegate calls the contract at the given address, with the option to limit the amount of + /// gas supplied. The return status indicates whether the call succeeded, and is nonzero on + /// failure. + /// + /// In both cases `return_data_len` will store the length of the result, the bytes of which + /// can be read via the `read_return_data` hostio. The bytes are not returned directly so that + /// the programmer can potentially save gas by choosing which subset of the return result + /// they'd like to copy. + /// + /// The semantics are equivalent to that of the EVM's [`DELEGATE_CALL`] opcode, including the + /// 63/64 gas rule. This means that supplying `u64::MAX` gas can be used to send as much as + /// possible. + /// + /// [`DELEGATE_CALL`]: https://www.evm.codes/#F4 + fn delegate_call_contract( + &mut self, + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, + ) -> Result { + let call = |api: &mut Self::A, contract, data: &_, left, req, _| { + api.delegate_call(contract, data, left, req) + }; + self.do_call( + contract, data, data_len, None, gas, ret_len, call, "delegate", + ) + } + + /// Static calls the contract at the given address, with the option to limit the amount of gas + /// supplied. The return status indicates whether the call succeeded, and is nonzero on + /// failure. + /// + /// In both cases `return_data_len` will store the length of the result, the bytes of which can + /// be read via the `read_return_data` hostio. The bytes are not returned directly so that the + /// programmer can potentially save gas by choosing which subset of the return result they'd + /// like to copy. + /// + /// The semantics are equivalent to that of the EVM's [`STATIC_CALL`] opcode, including the + /// 63/64 gas rule. This means that supplying `u64::MAX` gas can be used to send as much as + /// possible. + /// + /// [`STATIC_CALL`]: https://www.evm.codes/#FA + fn static_call_contract( + &mut self, + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, + ) -> Result { + let call = |api: &mut Self::A, contract, data: &_, left, req, _| { + api.static_call(contract, data, left, req) + }; + self.do_call(contract, data, data_len, None, gas, ret_len, call, "static") + } + + /// Performs one of the supported EVM calls. + /// Note that `value` must only be [`Some`] for normal calls. + fn do_call( + &mut self, + contract: GuestPtr, + calldata: GuestPtr, + calldata_len: u32, + value: Option, + gas: u64, + return_data_len: GuestPtr, + call: F, + name: &str, + ) -> Result + where + F: FnOnce( + &mut Self::A, + Address, + &[u8], + u64, + u64, + Option, + ) -> (u32, u64, UserOutcomeKind), + { + self.buy_ink(HOSTIO_INK + 3 * PTR_INK + EVM_API_INK)?; + self.pay_for_read(calldata_len)?; + self.pay_for_geth_bytes(calldata_len)?; + + let gas_left = self.gas_left()?; + let gas_req = gas.min(gas_left); + let contract = self.read_bytes20(contract)?; + let input = self.read_slice(calldata, calldata_len)?; + let value = value.map(|x| self.read_bytes32(x)).transpose()?; + let api = self.evm_api(); + + let (outs_len, gas_cost, status) = call(api, contract, &input, gas_left, gas_req, value); + self.buy_gas(gas_cost)?; + *self.evm_return_data_len() = outs_len; + self.write_u32(return_data_len, outs_len)?; + let status = status as u8; + + if self.evm_data().tracing { + let underscore = (!name.is_empty()).then_some("_").unwrap_or_default(); + let name = format!("{name}{underscore}call_contract"); + let value = value.into_iter().flatten(); + return trace!( + &name, + self, + [contract, be!(gas), value, &input], + [be!(outs_len), be!(status)], + status + ); + } + Ok(status) + } + + /// Deploys a new contract using the init code provided, which the EVM executes to construct + /// the code of the newly deployed contract. The init code must be written in EVM bytecode, but + /// the code it deploys can be that of a Stylus program. The code returned will be treated as + /// WASM if it begins with the EOF-inspired header `0xEFF000`. Otherwise the code will be + /// interpreted as that of a traditional EVM-style contract. See [`Deploying Stylus Programs`] + /// for more information on writing init code. + /// + /// On success, this hostio returns the address of the newly created account whose address is + /// a function of the sender and nonce. On failure the address will be `0`, `return_data_len` + /// will store the length of the revert data, the bytes of which can be read via the + /// `read_return_data` hostio. The semantics are equivalent to that of the EVM's [`CREATE`] + /// opcode, which notably includes the exact address returned. + /// + /// [`Deploying Stylus Programs`]: https://developer.arbitrum.io/TODO + /// [`CREATE`]: https://www.evm.codes/#f0 + fn create1( + &mut self, + code: GuestPtr, + code_len: u32, + endowment: GuestPtr, + contract: GuestPtr, + revert_data_len: GuestPtr, + ) -> Result<(), Self::Err> { + let call = |api: &mut Self::A, code, value, _, gas| api.create1(code, value, gas); + self.do_create( + code, + code_len, + endowment, + None, + contract, + revert_data_len, + 3 * PTR_INK + EVM_API_INK, + call, + "create1", + ) + } + + /// Deploys a new contract using the init code provided, which the EVM executes to construct + /// the code of the newly deployed contract. The init code must be written in EVM bytecode, but + /// the code it deploys can be that of a Stylus program. The code returned will be treated as + /// WASM if it begins with the EOF-inspired header `0xEFF000`. Otherwise the code will be + /// interpreted as that of a traditional EVM-style contract. See [`Deploying Stylus Programs`] + /// for more information on writing init code. + /// + /// On success, this hostio returns the address of the newly created account whose address is a + /// function of the sender, salt, and init code. On failure the address will be `0`, + /// `return_data_len` will store the length of the revert data, the bytes of which can be read + /// via the `read_return_data` hostio. The semantics are equivalent to that of the EVM's + /// `[CREATE2`] opcode, which notably includes the exact address returned. + /// + /// [`Deploying Stylus Programs`]: https://developer.arbitrum.io/TODO + /// [`CREATE2`]: https://www.evm.codes/#f5 + fn create2( + &mut self, + code: GuestPtr, + code_len: u32, + endowment: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_data_len: GuestPtr, + ) -> Result<(), Self::Err> { + let call = |api: &mut Self::A, code, value, salt: Option<_>, gas| { + api.create2(code, value, salt.unwrap(), gas) + }; + self.do_create( + code, + code_len, + endowment, + Some(salt), + contract, + revert_data_len, + 4 * PTR_INK + EVM_API_INK, + call, + "create2", + ) + } + + /// Deploys a contract via [`CREATE`] or [`CREATE2`]. + /// + /// [`CREATE`]: https://www.evm.codes/#f0 + /// [`CREATE2`]: https://www.evm.codes/#f5 + fn do_create( + &mut self, + code: GuestPtr, + code_len: u32, + endowment: GuestPtr, + salt: Option, + contract: GuestPtr, + revert_data_len: GuestPtr, + cost: u64, + call: F, + name: &str, + ) -> Result<(), Self::Err> + where + F: FnOnce(&mut Self::A, Vec, Bytes32, Option, u64) -> (Result
, u32, u64), + { + self.buy_ink(HOSTIO_INK + cost)?; + self.pay_for_read(code_len)?; + self.pay_for_geth_bytes(code_len)?; + + let code = self.read_slice(code, code_len)?; + let code_copy = self.evm_data().tracing.then(|| code.clone()); + + let endowment = self.read_bytes32(endowment)?; + let salt = salt.map(|x| self.read_bytes32(x)).transpose()?; + let gas = self.gas_left()?; + let api = self.evm_api(); + + let (result, ret_len, gas_cost) = call(api, code, endowment, salt, gas); + let result = result?; + + self.buy_gas(gas_cost)?; + *self.evm_return_data_len() = ret_len; + self.write_u32(revert_data_len, ret_len)?; + self.write_bytes20(contract, result)?; + + let salt = salt.into_iter().flatten(); + trace!( + name, + self, + [endowment, salt, code_copy.unwrap()], + [result, be!(ret_len)], + () + ) + } + + /// Copies the bytes of the last EVM call or deployment return result. Does not revert if out of + /// bounds, but rather copies the overlapping portion. The semantics are otherwise equivalent + /// to that of the EVM's [`RETURN_DATA_COPY`] opcode. + /// + /// Returns the number of bytes written. + /// + /// [`RETURN_DATA_COPY`]: https://www.evm.codes/#3e + fn read_return_data( + &mut self, + dest: GuestPtr, + offset: u32, + size: u32, + ) -> Result { + self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + + // pay for only as many bytes as could possibly be written + let max = self.evm_return_data_len().saturating_sub(offset); + self.pay_for_write(size.min(max))?; + + let ret_data = self.evm_api().get_return_data(); + let ret_data = ret_data.slice(); + let out_slice = arbutil::slice_with_runoff(&ret_data, offset, offset.saturating_add(size)); + + let out_len = out_slice.len() as u32; + if out_len > 0 { + self.write_slice(dest, out_slice)?; + } + trace!( + "read_return_data", + self, + [be!(offset), be!(size)], + out_slice.to_vec(), + out_len + ) + } + + /// Returns the length of the last EVM call or deployment return result, or `0` if neither have + /// happened during the program's execution. The semantics are equivalent to that of the EVM's + /// [`RETURN_DATA_SIZE`] opcode. + /// + /// [`RETURN_DATA_SIZE`]: https://www.evm.codes/#3d + fn return_data_size(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let len = *self.evm_return_data_len(); + trace!("return_data_size", self, be!(len), &[], len) + } + + /// Emits an EVM log with the given number of topics and data, the first bytes of which should + /// be the 32-byte-aligned topic data. The semantics are equivalent to that of the EVM's + /// [`LOG0`], [`LOG1`], [`LOG2`], [`LOG3`], and [`LOG4`] opcodes based on the number of topics + /// specified. Requesting more than `4` topics will induce a revert. + /// + /// [`LOG0`]: https://www.evm.codes/#a0 + /// [`LOG1`]: https://www.evm.codes/#a1 + /// [`LOG2`]: https://www.evm.codes/#a2 + /// [`LOG3`]: https://www.evm.codes/#a3 + /// [`LOG4`]: https://www.evm.codes/#a4 + fn emit_log(&mut self, data: GuestPtr, len: u32, topics: u32) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + if topics > 4 || len < topics * 32 { + Err(eyre!("bad topic data"))?; + } + self.pay_for_read(len)?; + self.pay_for_evm_log(topics, len - topics * 32)?; + + let data = self.read_slice(data, len)?; + self.evm_api().emit_log(data.clone(), topics)?; + trace!("emit_log", self, [be!(topics), data], &[]) + } + + /// Gets the ETH balance in wei of the account at the given address. + /// The semantics are equivalent to that of the EVM's [`BALANCE`] opcode. + /// + /// [`BALANCE`]: https://www.evm.codes/#31 + fn account_balance(&mut self, address: GuestPtr, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; + self.require_gas(evm::COLD_ACCOUNT_GAS)?; + let address = self.read_bytes20(address)?; + + let (balance, gas_cost) = self.evm_api().account_balance(address); + self.buy_gas(gas_cost)?; + self.write_bytes32(ptr, balance)?; + trace!("account_balance", self, address, balance) + } + + /// Gets a subset of the code from the account at the given address. The semantics are identical to that + /// of the EVM's [`EXT_CODE_COPY`] opcode, aside from one small detail: the write to the buffer `dest` will + /// stop after the last byte is written. This is unlike the EVM, which right pads with zeros in this scenario. + /// The return value is the number of bytes written, which allows the caller to detect if this has occured. + /// + /// [`EXT_CODE_COPY`]: https://www.evm.codes/#3C + fn account_code( + &mut self, + address: GuestPtr, + offset: u32, + size: u32, + dest: GuestPtr, + ) -> Result { + self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + self.require_gas(evm::COLD_ACCOUNT_GAS)?; // not necessary since we also check in Go + + let address = self.read_bytes20(address)?; + let gas = self.gas_left()?; + + // we pass `gas` to check if there's enough before loading from the db + let (code, gas_cost) = self.evm_api().account_code(address, gas); + self.buy_gas(gas_cost)?; + + let code = code.slice(); + self.pay_for_write(code.len() as u32)?; + + let out_slice = arbutil::slice_with_runoff(&code, offset, offset.saturating_add(size)); + let out_len = out_slice.len() as u32; + self.write_slice(dest, out_slice)?; + + trace!( + "account_code", + self, + [address, be!(offset), be!(size)], + out_slice.to_vec(), + out_len + ) + } + + /// Gets the size of the code in bytes at the given address. The semantics are equivalent + /// to that of the EVM's [`EXT_CODESIZE`]. + /// + /// [`EXT_CODESIZE`]: https://www.evm.codes/#3B + fn account_code_size(&mut self, address: GuestPtr) -> Result { + self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + self.require_gas(evm::COLD_ACCOUNT_GAS)?; // not necessary since we also check in Go + let address = self.read_bytes20(address)?; + let gas = self.gas_left()?; + + // we pass `gas` to check if there's enough before loading from the db + let (code, gas_cost) = self.evm_api().account_code(address, gas); + self.buy_gas(gas_cost)?; + + let code = code.slice(); + trace!("account_code_size", self, address, &[], code.len() as u32) + } + + /// Gets the code hash of the account at the given address. The semantics are equivalent + /// to that of the EVM's [`EXT_CODEHASH`] opcode. Note that the code hash of an account without + /// code will be the empty hash + /// `keccak("") = c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`. + /// + /// [`EXT_CODEHASH`]: https://www.evm.codes/#3F + fn account_codehash(&mut self, address: GuestPtr, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; + self.require_gas(evm::COLD_ACCOUNT_GAS)?; + let address = self.read_bytes20(address)?; + + let (hash, gas_cost) = self.evm_api().account_codehash(address); + self.buy_gas(gas_cost)?; + self.write_bytes32(ptr, hash)?; + trace!("account_codehash", self, address, hash) + } + + /// Gets the basefee of the current block. The semantics are equivalent to that of the EVM's + /// [`BASEFEE`] opcode. + /// + /// [`BASEFEE`]: https://www.evm.codes/#48 + fn block_basefee(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes32(ptr, self.evm_data().block_basefee)?; + trace!("block_basefee", self, &[], self.evm_data().block_basefee) + } + + /// Gets the coinbase of the current block, which on Arbitrum chains is the L1 batch poster's + /// address. This differs from Ethereum where the validator including the transaction + /// determines the coinbase. + fn block_coinbase(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes20(ptr, self.evm_data().block_coinbase)?; + trace!("block_coinbase", self, &[], self.evm_data().block_coinbase) + } + + /// Gets the gas limit of the current block. The semantics are equivalent to that of the EVM's + /// [`GAS_LIMIT`] opcode. Note that as of the time of this writing, `evm.codes` incorrectly + /// implies that the opcode returns the gas limit of the current transaction. When in doubt, + /// consult [`The Ethereum Yellow Paper`]. + /// + /// [`GAS_LIMIT`]: https://www.evm.codes/#45 + /// [`The Ethereum Yellow Paper`]: https://ethereum.github.io/yellowpaper/paper.pdf + fn block_gas_limit(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let limit = self.evm_data().block_gas_limit; + trace!("block_gas_limit", self, &[], be!(limit), limit) + } + + /// Gets a bounded estimate of the L1 block number at which the Sequencer sequenced the + /// transaction. See [`Block Numbers and Time`] for more information on how this value is + /// determined. + /// + /// [`Block Numbers and Time`]: https://developer.arbitrum.io/time + fn block_number(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let number = self.evm_data().block_number; + trace!("block_number", self, &[], be!(number), number) + } + + /// Gets a bounded estimate of the Unix timestamp at which the Sequencer sequenced the + /// transaction. See [`Block Numbers and Time`] for more information on how this value is + /// determined. + /// + /// [`Block Numbers and Time`]: https://developer.arbitrum.io/time + fn block_timestamp(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let timestamp = self.evm_data().block_timestamp; + trace!("block_timestamp", self, &[], be!(timestamp), timestamp) + } + + /// Gets the unique chain identifier of the Arbitrum chain. The semantics are equivalent to + /// that of the EVM's [`CHAIN_ID`] opcode. + /// + /// [`CHAIN_ID`]: https://www.evm.codes/#46 + fn chainid(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let chainid = self.evm_data().chainid; + trace!("chainid", self, &[], be!(chainid), chainid) + } + + /// Gets the address of the current program. The semantics are equivalent to that of the EVM's + /// [`ADDRESS`] opcode. + /// + /// [`ADDRESS`]: https://www.evm.codes/#30 + fn contract_address(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes20(ptr, self.evm_data().contract_address)?; + trace!( + "contract_address", + self, + &[], + self.evm_data().contract_address + ) + } + + /// Gets the amount of gas left after paying for the cost of this hostio. The semantics are + /// equivalent to that of the EVM's [`GAS`] opcode. + /// + /// [`GAS`]: https://www.evm.codes/#5a + fn evm_gas_left(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let gas = self.gas_left()?; + trace!("evm_gas_left", self, be!(gas), &[], gas) + } + + /// Gets the amount of ink remaining after paying for the cost of this hostio. The semantics + /// are equivalent to that of the EVM's [`GAS`] opcode, except the units are in ink. See + /// [`Ink and Gas`] for more information on Stylus's compute pricing. + /// + /// [`GAS`]: https://www.evm.codes/#5a + /// [`Ink and Gas`]: https://developer.arbitrum.io/TODO + fn evm_ink_left(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let ink = self.ink_ready()?; + trace!("evm_ink_left", self, be!(ink), &[], ink) + } + + /// Computes `value ÷ exponent` using 256-bit math, writing the result to the first. + /// The semantics are equivalent to that of the EVM's [`DIV`] opcode, which means that a `divisor` of `0` + /// writes `0` to `value`. + /// + /// [`DIV`]: https://www.evm.codes/#04 + fn math_div(&mut self, value: GuestPtr, divisor: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 3 * PTR_INK + pricing::DIV_INK)?; + let (a, a32) = self.read_u256(value)?; + let (b, b32) = self.read_u256(divisor)?; + + let result = a.checked_div(b).unwrap_or_default().into(); + self.write_bytes32(value, result)?; + trace!("math_div", self, [a32, b32], result) + } + + /// Computes `value % exponent` using 256-bit math, writing the result to the first. + /// The semantics are equivalent to that of the EVM's [`MOD`] opcode, which means that a `modulus` of `0` + /// writes `0` to `value`. + /// + /// [`MOD`]: https://www.evm.codes/#06 + fn math_mod(&mut self, value: GuestPtr, modulus: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 3 * PTR_INK + pricing::DIV_INK)?; + let (a, a32) = self.read_u256(value)?; + let (b, b32) = self.read_u256(modulus)?; + + let result = a.checked_rem(b).unwrap_or_default().into(); + self.write_bytes32(value, result)?; + trace!("math_mod", self, [a32, b32], result) + } + + /// Computes `value ^ exponent` using 256-bit math, writing the result to the first. + /// The semantics are equivalent to that of the EVM's [`EXP`] opcode. + /// + /// [`EXP`]: https://www.evm.codes/#0A + fn math_pow(&mut self, value: GuestPtr, exponent: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 3 * PTR_INK)?; + let (a, a32) = self.read_u256(value)?; + let (b, b32) = self.read_u256(exponent)?; + + self.pay_for_pow(&b32)?; + let result = a.wrapping_pow(b).into(); + self.write_bytes32(value, result)?; + trace!("math_pow", self, [a32, b32], result) + } + + /// Computes `(value + addend) % modulus` using 256-bit math, writing the result to the first. + /// The semantics are equivalent to that of the EVM's [`ADDMOD`] opcode, which means that a `modulus` of `0` + /// writes `0` to `value`. + /// + /// [`ADDMOD`]: https://www.evm.codes/#08 + fn math_add_mod( + &mut self, + value: GuestPtr, + addend: GuestPtr, + modulus: GuestPtr, + ) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 4 * PTR_INK + pricing::ADD_MOD_INK)?; + let (a, a32) = self.read_u256(value)?; + let (b, b32) = self.read_u256(addend)?; + let (c, c32) = self.read_u256(modulus)?; + + let result = a.add_mod(b, c).into(); + self.write_bytes32(value, result)?; + trace!("math_add_mod", self, [a32, b32, c32], result) + } + + /// Computes `(value * multiplier) % modulus` using 256-bit math, writing the result to the first. + /// The semantics are equivalent to that of the EVM's [`MULMOD`] opcode, which means that a `modulus` of `0` + /// writes `0` to `value`. + /// + /// [`MULMOD`]: https://www.evm.codes/#09 + fn math_mul_mod( + &mut self, + value: GuestPtr, + multiplier: GuestPtr, + modulus: GuestPtr, + ) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 4 * PTR_INK + pricing::MUL_MOD_INK)?; + let (a, a32) = self.read_u256(value)?; + let (b, b32) = self.read_u256(multiplier)?; + let (c, c32) = self.read_u256(modulus)?; + + let result = a.mul_mod(b, c).into(); + self.write_bytes32(value, result)?; + trace!("math_mul_mod", self, [a32, b32, c32], result) + } + + /// Whether the current call is reentrant. + fn msg_reentrant(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let reentrant = self.evm_data().reentrant; + trace!("msg_reentrant", self, &[], be!(reentrant), reentrant) + } + + /// Gets the address of the account that called the program. For normal L2-to-L2 transactions + /// the semantics are equivalent to that of the EVM's [`CALLER`] opcode, including in cases + /// arising from [`DELEGATE_CALL`]. + /// + /// For L1-to-L2 retryable ticket transactions, the top-level sender's address will be aliased. + /// See [`Retryable Ticket Address Aliasing`][aliasing] for more information on how this works. + /// + /// [`CALLER`]: https://www.evm.codes/#33 + /// [`DELEGATE_CALL`]: https://www.evm.codes/#f4 + /// [aliasing]: https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing + fn msg_sender(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes20(ptr, self.evm_data().msg_sender)?; + trace!("msg_sender", self, &[], self.evm_data().msg_sender) + } + + /// Get the ETH value in wei sent to the program. The semantics are equivalent to that of the + /// EVM's [`CALLVALUE`] opcode. + /// + /// [`CALLVALUE`]: https://www.evm.codes/#34 + fn msg_value(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes32(ptr, self.evm_data().msg_value)?; + trace!("msg_value", self, &[], self.evm_data().msg_value) + } + + /// Efficiently computes the [`keccak256`] hash of the given preimage. + /// The semantics are equivalent to that of the EVM's [`SHA3`] opcode. + /// + /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 + /// [`SHA3`]: https://www.evm.codes/#20 + fn native_keccak256( + &mut self, + input: GuestPtr, + len: u32, + output: GuestPtr, + ) -> Result<(), Self::Err> { + self.pay_for_keccak(len)?; + + let preimage = self.read_slice(input, len)?; + let digest = crypto::keccak(&preimage); + self.write_bytes32(output, digest.into())?; + trace!("native_keccak256", self, preimage, digest) + } + + /// Gets the gas price in wei per gas, which on Arbitrum chains equals the basefee. The + /// semantics are equivalent to that of the EVM's [`GAS_PRICE`] opcode. + /// + /// [`GAS_PRICE`]: https://www.evm.codes/#3A + fn tx_gas_price(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes32(ptr, self.evm_data().tx_gas_price)?; + trace!("tx_gas_price", self, &[], self.evm_data().tx_gas_price) + } + + /// Gets the price of ink in evm gas basis points. See [`Ink and Gas`] for more information on + /// Stylus's compute-pricing model. + /// + /// [`Ink and Gas`]: https://developer.arbitrum.io/TODO + fn tx_ink_price(&mut self) -> Result { + self.buy_ink(HOSTIO_INK)?; + let ink_price = self.pricing().ink_price; + trace!("tx_ink_price", self, &[], be!(ink_price), ink_price) + } + + /// Gets the top-level sender of the transaction. The semantics are equivalent to that of the + /// EVM's [`ORIGIN`] opcode. + /// + /// [`ORIGIN`]: https://www.evm.codes/#32 + fn tx_origin(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + PTR_INK)?; + self.write_bytes20(ptr, self.evm_data().tx_origin)?; + trace!("tx_origin", self, &[], self.evm_data().tx_origin) + } + + /// Pays for new pages as needed before the memory.grow opcode is invoked. + fn pay_for_memory_grow(&mut self, pages: u16) -> Result<(), Self::Err> { + if pages == 0 { + self.buy_ink(HOSTIO_INK)?; + return Ok(()); + } + let gas_cost = self.evm_api().add_pages(pages); // no sentry needed since the work happens after the hostio + self.buy_gas(gas_cost)?; + trace!("pay_for_memory_grow", self, be!(pages), &[]) + } + + /// Prints a UTF-8 encoded string to the console. Only available in debug mode. + fn console_log_text(&mut self, ptr: GuestPtr, len: u32) -> Result<(), Self::Err> { + let text = self.read_slice(ptr, len)?; + self.say(String::from_utf8_lossy(&text)); + trace!("console_log_text", self, text, &[]) + } + + /// Prints a value to the console. Only available in debug mode. + fn console_log>(&mut self, value: T) -> Result<(), Self::Err> { + let value = value.into(); + self.say(value); + trace!("console_log", self, [format!("{value}").as_bytes()], &[]) + } + + /// Prints and returns a value to the console. Only available in debug mode. + fn console_tee + Copy>(&mut self, value: T) -> Result { + self.say(value.into()); + Ok(value) + } +} diff --git a/arbitrator/wasm-libraries/user-host/Cargo.toml b/arbitrator/wasm-libraries/user-host/Cargo.toml new file mode 100644 index 000000000..15174397e --- /dev/null +++ b/arbitrator/wasm-libraries/user-host/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "user-host" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } +prover = { path = "../../prover/", default-features = false } +user-host-trait = { path = "../user-host-trait" } +wasmer-types = { path = "../../tools/wasmer/lib/types" } +eyre = "0.6.5" +fnv = "1.0.7" +hex = "0.4.3" diff --git a/arbitrator/wasm-libraries/user-host/src/host.rs b/arbitrator/wasm-libraries/user-host/src/host.rs new file mode 100644 index 000000000..abe55b8c1 --- /dev/null +++ b/arbitrator/wasm-libraries/user-host/src/host.rs @@ -0,0 +1,289 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::program::Program; +use arbutil::evm::user::UserOutcomeKind; +use caller_env::GuestPtr; +use user_host_trait::UserHost; + +#[link(wasm_import_module = "forward")] +extern "C" { + fn set_trap(); +} + +macro_rules! hostio { + ($($func:tt)*) => { + match Program::current().$($func)* { + Ok(value) => value, + Err(_) => { + set_trap(); + Default::default() + } + } + }; +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__read_args(ptr: GuestPtr) { + hostio!(read_args(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__exit_early(status: u32) { + hostio!(exit_early(status)); + Program::current().early_exit = Some(match status { + 0 => UserOutcomeKind::Success, + _ => UserOutcomeKind::Revert, + }); + set_trap(); +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__write_result(ptr: GuestPtr, len: u32) { + hostio!(write_result(ptr, len)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__storage_load_bytes32(key: GuestPtr, dest: GuestPtr) { + hostio!(storage_load_bytes32(key, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__storage_cache_bytes32(key: GuestPtr, value: GuestPtr) { + hostio!(storage_cache_bytes32(key, value)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__storage_flush_cache(clear: u32) { + hostio!(storage_flush_cache(clear != 0)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__transient_load_bytes32(key: GuestPtr, dest: GuestPtr) { + hostio!(transient_load_bytes32(key, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__transient_store_bytes32(key: GuestPtr, value: GuestPtr) { + hostio!(transient_store_bytes32(key, value)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + value: GuestPtr, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(call_contract(contract, data, data_len, value, gas, ret_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__delegate_call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(delegate_call_contract( + contract, data, data_len, gas, ret_len + )) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__static_call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(static_call_contract(contract, data, data_len, gas, ret_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__create1( + code: GuestPtr, + code_len: u32, + value: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) { + hostio!(create1(code, code_len, value, contract, revert_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__create2( + code: GuestPtr, + code_len: u32, + value: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) { + hostio!(create2(code, code_len, value, salt, contract, revert_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__read_return_data( + dest: GuestPtr, + offset: u32, + size: u32, +) -> u32 { + hostio!(read_return_data(dest, offset, size)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__return_data_size() -> u32 { + hostio!(return_data_size()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__emit_log(data: GuestPtr, len: u32, topics: u32) { + hostio!(emit_log(data, len, topics)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__account_balance(address: GuestPtr, ptr: GuestPtr) { + hostio!(account_balance(address, ptr)) +} +#[no_mangle] +pub unsafe extern "C" fn user_host__account_code( + address: GuestPtr, + offset: u32, + size: u32, + dest: GuestPtr, +) -> u32 { + hostio!(account_code(address, offset, size, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__account_code_size(address: GuestPtr) -> u32 { + hostio!(account_code_size(address)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__account_codehash(address: GuestPtr, ptr: GuestPtr) { + hostio!(account_codehash(address, ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__block_basefee(ptr: GuestPtr) { + hostio!(block_basefee(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__block_coinbase(ptr: GuestPtr) { + hostio!(block_coinbase(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__block_gas_limit() -> u64 { + hostio!(block_gas_limit()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__block_number() -> u64 { + hostio!(block_number()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__block_timestamp() -> u64 { + hostio!(block_timestamp()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__chainid() -> u64 { + hostio!(chainid()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__contract_address(ptr: GuestPtr) { + hostio!(contract_address(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__evm_gas_left() -> u64 { + hostio!(evm_gas_left()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__evm_ink_left() -> u64 { + hostio!(evm_ink_left()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__math_div(value: GuestPtr, divisor: GuestPtr) { + hostio!(math_div(value, divisor)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__math_mod(value: GuestPtr, modulus: GuestPtr) { + hostio!(math_mod(value, modulus)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__math_pow(value: GuestPtr, exponent: GuestPtr) { + hostio!(math_pow(value, exponent)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__math_add_mod( + value: GuestPtr, + addend: GuestPtr, + modulus: GuestPtr, +) { + hostio!(math_add_mod(value, addend, modulus)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__math_mul_mod( + value: GuestPtr, + multiplier: GuestPtr, + modulus: GuestPtr, +) { + hostio!(math_mul_mod(value, multiplier, modulus)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__msg_reentrant() -> u32 { + hostio!(msg_reentrant()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__msg_sender(ptr: GuestPtr) { + hostio!(msg_sender(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__msg_value(ptr: GuestPtr) { + hostio!(msg_value(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__native_keccak256(input: GuestPtr, len: u32, output: GuestPtr) { + hostio!(native_keccak256(input, len, output)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__tx_gas_price(ptr: GuestPtr) { + hostio!(tx_gas_price(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__tx_ink_price() -> u32 { + hostio!(tx_ink_price()) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__tx_origin(ptr: GuestPtr) { + hostio!(tx_origin(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__pay_for_memory_grow(pages: u16) { + hostio!(pay_for_memory_grow(pages)) +} diff --git a/arbitrator/wasm-libraries/user-host/src/ink.rs b/arbitrator/wasm-libraries/user-host/src/ink.rs new file mode 100644 index 000000000..e01e616e0 --- /dev/null +++ b/arbitrator/wasm-libraries/user-host/src/ink.rs @@ -0,0 +1,38 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::program::Program; +use prover::programs::{ + config::PricingParams, + prelude::{GasMeteredMachine, MachineMeter, MeteredMachine}, +}; + +#[link(wasm_import_module = "hostio")] +extern "C" { + fn user_ink_left() -> u64; + fn user_ink_status() -> u32; + fn user_set_ink(ink: u64, status: u32); +} + +impl MeteredMachine for Program { + fn ink_left(&self) -> MachineMeter { + unsafe { + match user_ink_status() { + 0 => MachineMeter::Ready(user_ink_left()), + _ => MachineMeter::Exhausted, + } + } + } + + fn set_meter(&mut self, meter: MachineMeter) { + unsafe { + user_set_ink(meter.ink(), meter.status()); + } + } +} + +impl GasMeteredMachine for Program { + fn pricing(&self) -> PricingParams { + self.config.pricing + } +} diff --git a/arbitrator/wasm-libraries/user-host/src/lib.rs b/arbitrator/wasm-libraries/user-host/src/lib.rs new file mode 100644 index 000000000..cd2d14285 --- /dev/null +++ b/arbitrator/wasm-libraries/user-host/src/lib.rs @@ -0,0 +1,7 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +mod host; +mod ink; +mod link; +mod program; diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs new file mode 100644 index 000000000..428611167 --- /dev/null +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -0,0 +1,280 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::program::Program; +use arbutil::{ + evm::{user::UserOutcomeKind, EvmData}, + format::DebugBytes, + heapify, Bytes20, Bytes32, +}; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use prover::{machine::Module, programs::config::StylusConfig}; + +// these hostio methods allow the replay machine to modify itself +#[link(wasm_import_module = "hostio")] +extern "C" { + fn wavm_link_module(hash: *const MemoryLeaf) -> u32; + fn wavm_unlink_module(); +} + +// these dynamic hostio methods allow introspection into user modules +#[link(wasm_import_module = "hostio")] +extern "C" { + fn program_set_ink(module: u32, ink: u64); + fn program_set_stack(module: u32, stack: u32); + fn program_ink_left(module: u32) -> u64; + fn program_ink_status(module: u32) -> u32; + fn program_stack_left(module: u32) -> u32; +} + +#[repr(C, align(256))] +struct MemoryLeaf([u8; 32]); + +/// Instruments and "activates" a user wasm, producing a unique module hash. +/// +/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. +/// The amount left is written back at the end of the call. +/// +/// pages_ptr: starts pointing to max allowed pages, returns number of pages used +#[no_mangle] +pub unsafe extern "C" fn programs__activate( + wasm_ptr: GuestPtr, + wasm_size: usize, + pages_ptr: GuestPtr, + asm_estimate_ptr: GuestPtr, + init_cost_ptr: GuestPtr, + cached_init_cost_ptr: GuestPtr, + version: u16, + debug: u32, + codehash: GuestPtr, + module_hash_ptr: GuestPtr, + gas_ptr: GuestPtr, + err_buf: GuestPtr, + err_buf_len: usize, +) -> usize { + let wasm = STATIC_MEM.read_slice(wasm_ptr, wasm_size); + let codehash = &read_bytes32(codehash); + let debug = debug != 0; + + let page_limit = STATIC_MEM.read_u16(pages_ptr); + let gas_left = &mut STATIC_MEM.read_u64(gas_ptr); + match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) { + Ok((module, data)) => { + STATIC_MEM.write_u64(gas_ptr, *gas_left); + STATIC_MEM.write_u16(pages_ptr, data.footprint); + STATIC_MEM.write_u32(asm_estimate_ptr, data.asm_estimate); + STATIC_MEM.write_u16(init_cost_ptr, data.init_cost); + STATIC_MEM.write_u16(cached_init_cost_ptr, data.cached_init_cost); + STATIC_MEM.write_slice(module_hash_ptr, module.hash().as_slice()); + 0 + } + Err(error) => { + let mut err_bytes = error.wrap_err("failed to activate").debug_bytes(); + err_bytes.truncate(err_buf_len); + STATIC_MEM.write_slice(err_buf, &err_bytes); + STATIC_MEM.write_u64(gas_ptr, 0); + STATIC_MEM.write_u16(pages_ptr, 0); + STATIC_MEM.write_u32(asm_estimate_ptr, 0); + STATIC_MEM.write_u16(init_cost_ptr, 0); + STATIC_MEM.write_u16(cached_init_cost_ptr, 0); + STATIC_MEM.write_slice(module_hash_ptr, Bytes32::default().as_slice()); + err_bytes.len() + } + } +} + +unsafe fn read_bytes32(ptr: GuestPtr) -> Bytes32 { + STATIC_MEM.read_fixed(ptr).into() +} + +unsafe fn read_bytes20(ptr: GuestPtr) -> Bytes20 { + STATIC_MEM.read_fixed(ptr).into() +} + +/// Links and creates user program +/// consumes both evm_data_handler and config_handler +/// returns module number +/// see program-exec for starting the user program +#[no_mangle] +pub unsafe extern "C" fn programs__new_program( + module_hash_ptr: GuestPtr, + calldata_ptr: GuestPtr, + calldata_size: usize, + config_box: u64, + evm_data_box: u64, + gas: u64, +) -> u32 { + let module_hash = read_bytes32(module_hash_ptr); + let calldata = STATIC_MEM.read_slice(calldata_ptr, calldata_size); + let config: StylusConfig = *Box::from_raw(config_box as _); + let evm_data: EvmData = *Box::from_raw(evm_data_box as _); + + // buy ink + let pricing = config.pricing; + let ink = pricing.gas_to_ink(gas); + + // link the program and ready its instrumentation + let module = wavm_link_module(&MemoryLeaf(*module_hash)); + program_set_ink(module, ink); + program_set_stack(module, config.max_depth); + + // provide arguments + Program::push_new(calldata, evm_data, module, config); + module +} + +/// Gets information about request according to id. +/// +/// # Safety +/// +/// `request_id` MUST be last request id returned from start_program or send_response. +#[no_mangle] +pub unsafe extern "C" fn programs__get_request(id: u32, len_ptr: GuestPtr) -> u32 { + let (req_type, len) = Program::current().request_handler().get_request_meta(id); + if len_ptr != GuestPtr(0) { + STATIC_MEM.write_u32(len_ptr, len as u32); + } + req_type +} + +/// Gets data associated with last request. +/// +/// # Safety +/// +/// `request_id` MUST be last request receieved +/// `data_ptr` MUST point to a buffer of at least the length returned by `get_request` +#[no_mangle] +pub unsafe extern "C" fn programs__get_request_data(id: u32, data_ptr: GuestPtr) { + let (_, data) = Program::current().request_handler().take_request(id); + STATIC_MEM.write_slice(data_ptr, &data); +} + +/// sets response for the next request made +/// id MUST be the id of last request made +/// see `program-exec::send_response` for sending this response to the program +#[no_mangle] +pub unsafe extern "C" fn programs__set_response( + id: u32, + gas: u64, + result_ptr: GuestPtr, + result_len: usize, + raw_data_ptr: GuestPtr, + raw_data_len: usize, +) { + let program = Program::current(); + program.request_handler().set_response( + id, + STATIC_MEM.read_slice(result_ptr, result_len), + STATIC_MEM.read_slice(raw_data_ptr, raw_data_len), + gas, + ); +} + +// removes the last created program +#[no_mangle] +pub unsafe extern "C" fn programs__pop() { + Program::pop(); + wavm_unlink_module(); +} + +// used by program-exec +// returns arguments_len +// module MUST be the last one returned from new_program +#[no_mangle] +pub unsafe extern "C" fn program_internal__args_len(module: u32) -> usize { + let program = Program::current(); + if program.module != module { + panic!("args_len requested for wrong module"); + } + program.args_len() +} + +/// used by program-exec +/// sets status of the last program and sends a program_done request +#[no_mangle] +pub unsafe extern "C" fn program_internal__set_done(mut status: UserOutcomeKind) -> u32 { + use UserOutcomeKind::*; + + let program = Program::current(); + let module = program.module; + let mut outs = program.outs.as_slice(); + let mut ink_left = program_ink_left(module); + + // apply any early exit codes + if let Some(early) = program.early_exit { + status = early; + } + + // check if instrumentation stopped the program + if program_ink_status(module) != 0 { + status = OutOfInk; + outs = &[]; + ink_left = 0; + } + if program_stack_left(module) == 0 { + status = OutOfStack; + outs = &[]; + ink_left = 0; + } + + let gas_left = program.config.pricing.ink_to_gas(ink_left); + + let mut output = Vec::with_capacity(8 + outs.len()); + output.extend(gas_left.to_be_bytes()); + output.extend(outs); + program + .request_handler() + .set_request(status as u32, &output) +} + +/// Creates a `StylusConfig` from its component parts. +#[no_mangle] +pub unsafe extern "C" fn programs__create_stylus_config( + version: u16, + max_depth: u32, + ink_price: u32, + _debug: u32, +) -> u64 { + let config = StylusConfig::new(version, max_depth, ink_price); + heapify(config) as u64 +} + +/// Creates an `EvmData` handler from its component parts. +/// +#[no_mangle] +pub unsafe extern "C" fn programs__create_evm_data( + block_basefee_ptr: GuestPtr, + chainid: u64, + block_coinbase_ptr: GuestPtr, + block_gas_limit: u64, + block_number: u64, + block_timestamp: u64, + contract_address_ptr: GuestPtr, + module_hash_ptr: GuestPtr, + msg_sender_ptr: GuestPtr, + msg_value_ptr: GuestPtr, + tx_gas_price_ptr: GuestPtr, + tx_origin_ptr: GuestPtr, + cached: u32, + reentrant: u32, +) -> u64 { + let evm_data = EvmData { + block_basefee: read_bytes32(block_basefee_ptr), + cached: cached != 0, + chainid, + block_coinbase: read_bytes20(block_coinbase_ptr), + block_gas_limit, + block_number, + block_timestamp, + contract_address: read_bytes20(contract_address_ptr), + module_hash: read_bytes32(module_hash_ptr), + msg_sender: read_bytes20(msg_sender_ptr), + msg_value: read_bytes32(msg_value_ptr), + tx_gas_price: read_bytes32(tx_gas_price_ptr), + tx_origin: read_bytes20(tx_origin_ptr), + reentrant, + return_data_len: 0, + tracing: false, + }; + heapify(evm_data) as u64 +} diff --git a/arbitrator/wasm-libraries/user-host/src/program.rs b/arbitrator/wasm-libraries/user-host/src/program.rs new file mode 100644 index 000000000..4199a691f --- /dev/null +++ b/arbitrator/wasm-libraries/user-host/src/program.rs @@ -0,0 +1,273 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use arbutil::{ + evm::{ + api::{EvmApiMethod, VecReader, EVM_API_METHOD_REQ_OFFSET}, + req::{EvmApiRequestor, RequestHandler}, + user::UserOutcomeKind, + EvmData, + }, + Color, +}; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use core::sync::atomic::{compiler_fence, Ordering}; +use eyre::{eyre, Result}; +use prover::programs::prelude::*; +use std::fmt::Display; +use user_host_trait::UserHost; +use wasmer_types::{Pages, WASM_PAGE_SIZE}; + +// allows introspection into user modules +#[link(wasm_import_module = "hostio")] +extern "C" { + fn program_memory_size(module: u32) -> u32; +} + +/// Signifies an out-of-bounds memory access was requested. +pub(crate) struct MemoryBoundsError; + +impl From for eyre::ErrReport { + fn from(_: MemoryBoundsError) -> Self { + eyre!("memory access out of bounds") + } +} + +/// The list of active programs. The current program is always the last. +/// +/// Note that this data-structure may re-alloc while references to [`Program`] are held. +/// This is sound due to [`Box`] providing a level of indirection. +/// +/// Normal Rust rules would suggest using a [`Vec`] of cells would be better. The issue is that, +/// should an error guard recover, this WASM will reset to an earlier state but with the current +/// memory. This means that stack unwinding won't happen, rendering these primitives unhelpful. +#[allow(clippy::vec_box)] +static mut PROGRAMS: Vec> = vec![]; + +static mut LAST_REQUEST_ID: u32 = 0x10000; + +#[derive(Clone)] +pub(crate) struct UserHostRequester { + data: Option>, + answer: Option<(Vec, VecReader, u64)>, + req_type: u32, + id: u32, +} + +impl UserHostRequester { + pub fn default() -> Self { + Self { + req_type: 0, + data: None, + answer: None, + id: 0, + } + } +} + +/// An active user program. +pub(crate) struct Program { + /// Arguments passed via the VM. + pub args: Vec, + /// Output generated by the program. + pub outs: Vec, + /// Mechanism for calling back into Geth. + pub evm_api: EvmApiRequestor, + /// EVM Context info. + pub evm_data: EvmData, + /// WAVM module index. + pub module: u32, + /// Call configuration. + pub config: StylusConfig, + /// Whether the program exited early. + pub early_exit: Option, +} + +#[link(wasm_import_module = "hostio")] +extern "C" { + fn program_request(status: u32) -> u32; +} + +impl UserHostRequester { + #[no_mangle] + pub unsafe fn set_response( + &mut self, + req_id: u32, + result: Vec, + raw_data: Vec, + gas: u64, + ) { + self.answer = Some((result, VecReader::new(raw_data), gas)); + if req_id != self.id { + panic!("bad req id returning from send_request") + } + compiler_fence(Ordering::SeqCst); + } + + pub unsafe fn set_request(&mut self, req_type: u32, data: &[u8]) -> u32 { + LAST_REQUEST_ID += 1; + self.id = LAST_REQUEST_ID; + self.req_type = req_type; + self.data = Some(data.to_vec()); + self.answer = None; + self.id + } + + pub unsafe fn get_request_meta(&self, id: u32) -> (u32, usize) { + if self.id != id { + panic!("get_request got wrong id"); + } + let size = self.data.as_ref().expect("no data get_request_meta").len(); + (self.req_type, size) + } + + pub unsafe fn take_request(&mut self, id: u32) -> (u32, Vec) { + if self.id != id { + panic!("get_request got wrong id"); + } + let data = self.data.take().expect("no request on take_request"); + (self.req_type, data) + } + + #[no_mangle] + unsafe fn send_request(&mut self, req_type: u32, data: Vec) -> (Vec, VecReader, u64) { + let req_id = self.set_request(req_type, &data); + compiler_fence(Ordering::SeqCst); + + let got_id = program_request(req_id); + compiler_fence(Ordering::SeqCst); + + if got_id != req_id { + panic!("bad req id returning from send_request") + } + self.answer.take().unwrap() + } +} + +impl RequestHandler for UserHostRequester { + fn request( + &mut self, + req_type: EvmApiMethod, + req_data: impl AsRef<[u8]>, + ) -> (Vec, VecReader, u64) { + unsafe { + self.send_request( + req_type as u32 + EVM_API_METHOD_REQ_OFFSET, + req_data.as_ref().to_vec(), + ) + } + } +} + +impl Program { + /// Adds a new program, making it current. + pub fn push_new(args: Vec, evm_data: EvmData, module: u32, config: StylusConfig) { + let program = Self { + args, + outs: vec![], + evm_api: EvmApiRequestor::new(UserHostRequester::default()), + evm_data, + module, + config, + early_exit: None, + }; + unsafe { PROGRAMS.push(Box::new(program)) } + } + + /// Removes the current program + pub fn pop() { + unsafe { + PROGRAMS.pop().expect("no program"); + } + } + + /// Provides a reference to the current program. + pub fn current() -> &'static mut Self { + unsafe { PROGRAMS.last_mut().expect("no program") } + } + + /// Reads the program's memory size in pages. + fn memory_size(&self) -> Pages { + unsafe { Pages(program_memory_size(self.module)) } + } + + /// Reads the program's memory size in bytes. + fn memory_size_bytes(&self) -> u64 { + self.memory_size().0 as u64 * WASM_PAGE_SIZE as u64 + } + + /// Provides the length of the program's calldata in bytes. + pub fn args_len(&self) -> usize { + self.args.len() + } + + /// Ensures an access is within bounds + fn check_memory_access(&self, ptr: GuestPtr, bytes: u32) -> Result<(), MemoryBoundsError> { + let end = ptr.to_u64() + bytes as u64; + if end > self.memory_size_bytes() { + return Err(MemoryBoundsError); + } + Ok(()) + } + + pub fn request_handler(&mut self) -> &mut UserHostRequester { + self.evm_api.request_handler() + } +} + +#[allow(clippy::unit_arg)] +impl UserHost for Program { + type Err = eyre::ErrReport; + type MemoryErr = MemoryBoundsError; + type A = EvmApiRequestor; + + fn args(&self) -> &[u8] { + &self.args + } + + fn outs(&mut self) -> &mut Vec { + &mut self.outs + } + + fn evm_api(&mut self) -> &mut Self::A { + &mut self.evm_api + } + + fn evm_data(&self) -> &EvmData { + &self.evm_data + } + + fn evm_return_data_len(&mut self) -> &mut u32 { + &mut self.evm_data.return_data_len + } + + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, MemoryBoundsError> { + self.check_memory_access(ptr, len)?; + unsafe { Ok(STATIC_MEM.read_slice(ptr, len as usize)) } + } + + fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], MemoryBoundsError> { + self.read_slice(ptr, N as u32) + .map(|x| x.try_into().unwrap()) + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), MemoryBoundsError> { + self.check_memory_access(ptr, 4)?; + unsafe { Ok(STATIC_MEM.write_u32(ptr, x)) } + } + + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), MemoryBoundsError> { + self.check_memory_access(ptr, src.len() as u32)?; + unsafe { Ok(STATIC_MEM.write_slice(ptr, src)) } + } + + fn say(&self, text: D) { + println!("{} {text}", "Stylus says:".yellow()); + } + + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: u64) { + let args = hex::encode(args); + let outs = hex::encode(outs); + println!("Error: unexpected hostio tracing info for {name} while proving: {args}, {outs}"); + } +} diff --git a/arbitrator/wasm-libraries/user-test/Cargo.toml b/arbitrator/wasm-libraries/user-test/Cargo.toml new file mode 100644 index 000000000..aad9d8ec2 --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "user-test" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } +prover = { path = "../../prover/", default-features = false } +user-host-trait = { path = "../user-host-trait" } +eyre = "0.6.5" +fnv = "1.0.7" +hex = "0.4.3" +lazy_static = "1.4.0" +parking_lot = "0.12.1" diff --git a/arbitrator/wasm-libraries/user-test/src/host.rs b/arbitrator/wasm-libraries/user-test/src/host.rs new file mode 100644 index 000000000..f2912eaae --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/src/host.rs @@ -0,0 +1,238 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::program::Program; +use caller_env::GuestPtr; +use user_host_trait::UserHost; + +macro_rules! hostio { + ($($func:tt)*) => { + match Program::current().$($func)* { + Ok(value) => value, + Err(error) => panic!("{error}"), + } + }; +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__read_args(ptr: GuestPtr) { + hostio!(read_args(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__exit_early(status: u32) { + hostio!(exit_early(status)); +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__write_result(ptr: GuestPtr, len: u32) { + hostio!(write_result(ptr, len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__storage_load_bytes32(key: GuestPtr, dest: GuestPtr) { + hostio!(storage_load_bytes32(key, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__storage_cache_bytes32(key: GuestPtr, value: GuestPtr) { + hostio!(storage_cache_bytes32(key, value)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__storage_flush_cache(clear: u32) { + hostio!(storage_flush_cache(clear != 0)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__transient_load_bytes32(key: GuestPtr, dest: GuestPtr) { + hostio!(transient_load_bytes32(key, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__transient_store_bytes32(key: GuestPtr, value: GuestPtr) { + hostio!(transient_store_bytes32(key, value)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + value: GuestPtr, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(call_contract(contract, data, data_len, value, gas, ret_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__delegate_call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(delegate_call_contract( + contract, data, data_len, gas, ret_len + )) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__static_call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(static_call_contract(contract, data, data_len, gas, ret_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__create1( + code: GuestPtr, + code_len: u32, + value: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) { + hostio!(create1(code, code_len, value, contract, revert_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__create2( + code: GuestPtr, + code_len: u32, + value: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) { + hostio!(create2(code, code_len, value, salt, contract, revert_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__read_return_data(dest: GuestPtr, offset: u32, size: u32) -> u32 { + hostio!(read_return_data(dest, offset, size)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__return_data_size() -> u32 { + hostio!(return_data_size()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__emit_log(data: GuestPtr, len: u32, topics: u32) { + hostio!(emit_log(data, len, topics)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_balance(address: GuestPtr, ptr: GuestPtr) { + hostio!(account_balance(address, ptr)) +} +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_code( + address: GuestPtr, + offset: u32, + size: u32, + dest: GuestPtr, +) -> u32 { + hostio!(account_code(address, offset, size, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_code_size(address: GuestPtr) -> u32 { + hostio!(account_code_size(address)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_codehash(address: GuestPtr, ptr: GuestPtr) { + hostio!(account_codehash(address, ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_basefee(ptr: GuestPtr) { + hostio!(block_basefee(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_coinbase(ptr: GuestPtr) { + hostio!(block_coinbase(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_gas_limit() -> u64 { + hostio!(block_gas_limit()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_number() -> u64 { + hostio!(block_number()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_timestamp() -> u64 { + hostio!(block_timestamp()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__chainid() -> u64 { + hostio!(chainid()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__contract_address(ptr: GuestPtr) { + hostio!(contract_address(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__evm_gas_left() -> u64 { + hostio!(evm_gas_left()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__evm_ink_left() -> u64 { + hostio!(evm_ink_left()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__msg_reentrant() -> u32 { + hostio!(msg_reentrant()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__msg_sender(ptr: GuestPtr) { + hostio!(msg_sender(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__msg_value(ptr: GuestPtr) { + hostio!(msg_value(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__native_keccak256(input: GuestPtr, len: u32, output: GuestPtr) { + hostio!(native_keccak256(input, len, output)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__tx_gas_price(ptr: GuestPtr) { + hostio!(tx_gas_price(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__tx_ink_price() -> u32 { + hostio!(tx_ink_price()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__tx_origin(ptr: GuestPtr) { + hostio!(tx_origin(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__pay_for_memory_grow(pages: u16) { + hostio!(pay_for_memory_grow(pages)) +} diff --git a/arbitrator/wasm-libraries/user-test/src/ink.rs b/arbitrator/wasm-libraries/user-test/src/ink.rs new file mode 100644 index 000000000..fca658e59 --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/src/ink.rs @@ -0,0 +1,38 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{program::Program, CONFIG}; +use prover::programs::{ + config::PricingParams, + prelude::{GasMeteredMachine, MachineMeter, MeteredMachine}, +}; + +#[link(wasm_import_module = "hostio")] +extern "C" { + fn user_ink_left() -> u64; + fn user_ink_status() -> u32; + fn user_set_ink(ink: u64, status: u32); +} + +impl MeteredMachine for Program { + fn ink_left(&self) -> MachineMeter { + unsafe { + match user_ink_status() { + 0 => MachineMeter::Ready(user_ink_left()), + _ => MachineMeter::Exhausted, + } + } + } + + fn set_meter(&mut self, meter: MachineMeter) { + unsafe { + user_set_ink(meter.ink(), meter.status()); + } + } +} + +impl GasMeteredMachine for Program { + fn pricing(&self) -> PricingParams { + unsafe { CONFIG.unwrap().pricing } + } +} diff --git a/arbitrator/wasm-libraries/user-test/src/lib.rs b/arbitrator/wasm-libraries/user-test/src/lib.rs new file mode 100644 index 000000000..ffb8d4a28 --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/src/lib.rs @@ -0,0 +1,55 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![allow(clippy::missing_safety_doc)] + +use arbutil::{evm::EvmData, Bytes32}; +use fnv::FnvHashMap as HashMap; +use lazy_static::lazy_static; +use parking_lot::Mutex; +use prover::programs::prelude::StylusConfig; + +pub mod host; +mod ink; +mod program; + +pub(crate) static mut ARGS: Vec = vec![]; +pub(crate) static mut OUTS: Vec = vec![]; +pub(crate) static mut LOGS: Vec> = vec![]; +pub(crate) static mut CONFIG: Option = None; +pub(crate) static mut OPEN_PAGES: u16 = 0; +pub(crate) static mut EVER_PAGES: u16 = 0; + +lazy_static! { + static ref KEYS: Mutex> = Mutex::new(HashMap::default()); + static ref EVM_DATA: EvmData = EvmData::default(); +} + +#[no_mangle] +pub unsafe extern "C" fn user_test__prepare( + len: usize, + version: u16, + max_depth: u32, + ink_price: u32, +) -> *const u8 { + let config = StylusConfig::new(version, max_depth, ink_price); + CONFIG = Some(config); + ARGS = vec![0; len]; + ARGS.as_ptr() +} + +#[no_mangle] +pub unsafe extern "C" fn user_test__set_pages(pages: u16) { + OPEN_PAGES = OPEN_PAGES.saturating_add(pages); + EVER_PAGES = EVER_PAGES.max(OPEN_PAGES); +} + +#[no_mangle] +pub unsafe extern "C" fn user_test__get_outs_ptr() -> *const u8 { + OUTS.as_ptr() +} + +#[no_mangle] +pub unsafe extern "C" fn user_test__get_outs_len() -> usize { + OUTS.len() +} diff --git a/arbitrator/wasm-libraries/user-test/src/program.rs b/arbitrator/wasm-libraries/user-test/src/program.rs new file mode 100644 index 000000000..c56ea52ad --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/src/program.rs @@ -0,0 +1,220 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ARGS, EVER_PAGES, EVM_DATA, KEYS, LOGS, OPEN_PAGES, OUTS}; +use arbutil::{ + evm::{ + api::{EvmApi, VecReader}, + user::UserOutcomeKind, + EvmData, + }, + Bytes20, Bytes32, Color, +}; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use eyre::{eyre, Result}; +use prover::programs::memory::MemoryModel; +use std::fmt::Display; +use user_host_trait::UserHost; + +/// Signifies an out-of-bounds memory access was requested. +pub struct MemoryBoundsError; + +impl From for eyre::ErrReport { + fn from(_: MemoryBoundsError) -> Self { + eyre!("memory access out of bounds") + } +} + +/// Mock type representing a `user_host::Program` +pub struct Program { + evm_api: MockEvmApi, +} + +#[allow(clippy::unit_arg)] +impl UserHost for Program { + type Err = eyre::ErrReport; + type MemoryErr = MemoryBoundsError; + type A = MockEvmApi; + + fn args(&self) -> &[u8] { + unsafe { &ARGS } + } + + fn outs(&mut self) -> &mut Vec { + unsafe { &mut OUTS } + } + + fn evm_api(&mut self) -> &mut Self::A { + &mut self.evm_api + } + + fn evm_data(&self) -> &EvmData { + &EVM_DATA + } + + fn evm_return_data_len(&mut self) -> &mut u32 { + unimplemented!() + } + + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, MemoryBoundsError> { + self.check_memory_access(ptr, len)?; + unsafe { Ok(STATIC_MEM.read_slice(ptr, len as usize)) } + } + + fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], MemoryBoundsError> { + self.read_slice(ptr, N as u32) + .map(|x| x.try_into().unwrap()) + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), MemoryBoundsError> { + self.check_memory_access(ptr, 4)?; + unsafe { Ok(STATIC_MEM.write_u32(ptr, x)) } + } + + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), MemoryBoundsError> { + self.check_memory_access(ptr, src.len() as u32)?; + unsafe { Ok(STATIC_MEM.write_slice(ptr, src)) } + } + + fn say(&self, text: D) { + println!("{} {text}", "Stylus says:".yellow()); + } + + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: u64) { + let args = hex::encode(args); + let outs = hex::encode(outs); + println!("Error: unexpected hostio tracing info for {name} while proving: {args}, {outs}"); + } +} + +impl Program { + pub fn current() -> Self { + Self { + evm_api: MockEvmApi, + } + } + + fn check_memory_access(&self, _ptr: GuestPtr, _bytes: u32) -> Result<(), MemoryBoundsError> { + Ok(()) // pretend we did a check + } +} + +pub struct MockEvmApi; + +impl EvmApi for MockEvmApi { + fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + let value = KEYS.lock().get(&key).cloned().unwrap_or_default(); + (value, 2100) // pretend worst case + } + + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + KEYS.lock().insert(key, value); + 0 + } + + fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result { + Ok(22100 * KEYS.lock().len() as u64) // pretend worst case + } + + fn get_transient_bytes32(&mut self, _key: Bytes32) -> Bytes32 { + unimplemented!() + } + + fn set_transient_bytes32(&mut self, _key: Bytes32, _value: Bytes32) -> Result<()> { + unimplemented!() + } + + /// Simulates a contract call. + /// Note: this call function is for testing purposes only and deviates from onchain behavior. + fn contract_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas_left: u64, + _gas_req: u64, + _value: Bytes32, + ) -> (u32, u64, UserOutcomeKind) { + unimplemented!() + } + + fn delegate_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas_left: u64, + _gas_req: u64, + ) -> (u32, u64, UserOutcomeKind) { + unimplemented!() + } + + fn static_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas_left: u64, + _gas_req: u64, + ) -> (u32, u64, UserOutcomeKind) { + unimplemented!() + } + + fn create1( + &mut self, + _code: Vec, + _endowment: Bytes32, + _gas: u64, + ) -> (Result, u32, u64) { + unimplemented!() + } + + fn create2( + &mut self, + _code: Vec, + _endowment: Bytes32, + _salt: Bytes32, + _gas: u64, + ) -> (Result, u32, u64) { + unimplemented!() + } + + fn get_return_data(&self) -> VecReader { + unimplemented!() + } + + fn emit_log(&mut self, data: Vec, _topics: u32) -> Result<()> { + unsafe { LOGS.push(data) }; + Ok(()) + } + + fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, u64) { + unimplemented!() + } + + fn account_code(&mut self, _address: Bytes20, _gas_left: u64) -> (VecReader, u64) { + unimplemented!() + } + + fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) { + unimplemented!() + } + + fn add_pages(&mut self, pages: u16) -> u64 { + let model = MemoryModel::new(2, 1000); + unsafe { + let (open, ever) = (OPEN_PAGES, EVER_PAGES); + OPEN_PAGES = OPEN_PAGES.saturating_add(pages); + EVER_PAGES = EVER_PAGES.max(OPEN_PAGES); + model.gas_cost(pages, open, ever) + } + } + + fn capture_hostio( + &mut self, + _name: &str, + _args: &[u8], + _outs: &[u8], + _start_ink: u64, + _end_ink: u64, + ) { + unimplemented!() + } +} diff --git a/arbitrator/wasm-libraries/wasi-stub/Cargo.toml b/arbitrator/wasm-libraries/wasi-stub/Cargo.toml index ebba324fe..698c1e0f2 100644 --- a/arbitrator/wasm-libraries/wasi-stub/Cargo.toml +++ b/arbitrator/wasm-libraries/wasi-stub/Cargo.toml @@ -8,3 +8,6 @@ publish = false crate-type = ["cdylib"] [dependencies] +paste = { version = "1.0.14" } +caller-env = { path = "../../caller-env/", default-features = false, features = ["static_caller"] } +wee_alloc = "0.4.2" diff --git a/arbitrator/wasm-libraries/wasi-stub/src/lib.rs b/arbitrator/wasm-libraries/wasi-stub/src/lib.rs index d10b70807..2f237dcb4 100644 --- a/arbitrator/wasm-libraries/wasi-stub/src/lib.rs +++ b/arbitrator/wasm-libraries/wasi-stub/src/lib.rs @@ -1,17 +1,20 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::missing_safety_doc)] // TODO: require safety docs #![no_std] -const ERRNO_BADF: u16 = 8; -const ERRNO_INTVAL: u16 = 28; +use caller_env::{self, wasip1_stub::Errno, GuestPtr}; +use paste::paste; +use wee_alloc::WeeAlloc; -#[allow(dead_code)] extern "C" { - fn wavm_caller_load8(ptr: usize) -> u8; - fn wavm_caller_load32(ptr: usize) -> u32; - fn wavm_caller_store8(ptr: usize, val: u8); - fn wavm_caller_store32(ptr: usize, val: u32); fn wavm_halt_and_set_finished() -> !; } +#[global_allocator] +static ALLOC: WeeAlloc = WeeAlloc::INIT; + #[panic_handler] unsafe fn panic(_: &core::panic::PanicInfo) -> ! { core::arch::wasm32::unreachable() @@ -26,89 +29,157 @@ pub unsafe extern "C" fn wasi_snapshot_preview1__proc_exit(code: u32) -> ! { } } -#[no_mangle] -pub unsafe extern "C" fn env__exit(code: u32) { - if code == 0 { - wavm_halt_and_set_finished() - } else { - core::arch::wasm32::unreachable() - } +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + paste! { + $( + #[no_mangle] + pub unsafe extern "C" fn []($($arg_name : $arg_type),*) -> $return_type { + caller_env::wasip1_stub::$func_name( + &mut caller_env::static_caller::STATIC_MEM, + &mut caller_env::static_caller::STATIC_ENV, + $($arg_name),* + ) + } + )* + } + }; } -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__environ_sizes_get( - length_ptr: usize, - data_size_ptr: usize, -) -> u16 { - wavm_caller_store32(length_ptr, 0); - wavm_caller_store32(data_size_ptr, 0); - 0 -} +wrap! { + fn clock_time_get( + clock_id: u32, + precision: u64, + time_ptr: GuestPtr + ) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_write( - fd: usize, - iovecs_ptr: usize, - iovecs_len: usize, - ret_ptr: usize, -) -> u16 { - if fd != 1 && fd != 2 { - return ERRNO_BADF; - } - let mut size = 0; - for i in 0..iovecs_len { - let ptr = iovecs_ptr + i * 8; - size += wavm_caller_load32(ptr + 4); - } - wavm_caller_store32(ret_ptr, size); - 0 -} + fn random_get(buf: GuestPtr, len: u32) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__environ_get(_: usize, _: usize) -> u16 { - ERRNO_INTVAL -} + fn environ_sizes_get(length_ptr: GuestPtr, data_size_ptr: GuestPtr) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_close(_: usize) -> u16 { - ERRNO_BADF -} + fn fd_write( + fd: u32, + iovecs_ptr: GuestPtr, + iovecs_len: u32, + ret_ptr: GuestPtr + ) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_read( - _: usize, - _: usize, - _: usize, - _: usize, -) -> u16 { - ERRNO_BADF -} + fn environ_get(a: GuestPtr, b: GuestPtr) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_open( - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, - _: u64, - _: u64, - _: usize, - _: usize, -) -> u16 { - ERRNO_BADF -} + fn fd_close(fd: u32) -> Errno; + fn fd_read(a: u32, b: u32, c: u32, d: u32) -> Errno; + fn fd_readdir( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_prestat_get(_: usize, _: usize) -> u16 { - ERRNO_BADF -} + fn fd_sync(a: u32) -> Errno; -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_prestat_dir_name( - _: usize, - _: usize, - _: usize, -) -> u16 { - ERRNO_BADF + fn fd_seek( + fd: u32, + offset: u64, + whence: u8, + filesize: u32 + ) -> Errno; + + fn fd_datasync(fd: u32) -> Errno; + + fn path_open( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u64, + g: u64, + h: u32, + i: u32 + ) -> Errno; + + fn path_create_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_remove_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_readlink( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_rename( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_filestat_get( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32 + ) -> Errno; + + fn path_unlink_file(a: u32, b: u32, c: u32) -> Errno; + + fn fd_prestat_get(a: u32, b: u32) -> Errno; + fn fd_prestat_dir_name(a: u32, b: u32, c: u32) -> Errno; + + fn fd_filestat_get(fd: u32, filestat: u32) -> Errno; + fn fd_filestat_set_size(fd: u32, size: u64) -> Errno; + + fn fd_pread( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_pwrite( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn sock_accept(fd: u32, a: u32, b: u32) -> Errno; + fn sock_shutdown(a: u32, b: u32) -> Errno; + + fn sched_yield() -> Errno; + + fn args_sizes_get( + length_ptr: GuestPtr, + data_size_ptr: GuestPtr + ) -> Errno; + + fn args_get(argv_buf: GuestPtr, data_buf: GuestPtr) -> Errno; + + fn fd_fdstat_get(a: u32, b: u32) -> Errno; + fn fd_fdstat_set_flags(a: u32, b: u32) -> Errno; + + fn poll_oneoff( + in_subs: GuestPtr, + out_evt: GuestPtr, + nsubscriptions: u32, + nevents_ptr: GuestPtr + ) -> Errno } diff --git a/arbitrator/wasm-testsuite/Cargo.lock b/arbitrator/wasm-testsuite/Cargo.lock index 60e48adf1..c6f946b8e 100644 --- a/arbitrator/wasm-testsuite/Cargo.lock +++ b/arbitrator/wasm-testsuite/Cargo.lock @@ -2,6 +2,32 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli 0.27.0", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -11,6 +37,14 @@ dependencies = [ "winapi", ] +[[package]] +name = "arbutil" +version = "0.1.0" +dependencies = [ + "sha3 0.10.6", + "siphasher", +] + [[package]] name = "arrayvec" version = "0.7.2" @@ -34,6 +68,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bincode" version = "1.3.3" @@ -59,6 +108,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.2.1" @@ -85,6 +143,45 @@ dependencies = [ "libc", ] +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytecheck" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + [[package]] name = "cc" version = "1.0.73" @@ -112,6 +209,85 @@ dependencies = [ "vec_map", ] +[[package]] +name = "corosensei" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "scopeguard", + "windows-sys 0.33.0", +] + +[[package]] +name = "cranelift-bforest" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529ffacce2249ac60edba2941672dfedf3d96558b415d0d8083cd007456e0f55" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427d105f617efc8cb55f8d036a7fded2e227892d8780b4985e5551f8d27c4a92" +dependencies = [ + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli 0.26.2", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551674bed85b838d45358e3eab4f0ffaa6790c70dc08184204b9a54b41cdb7d1" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b3a63ae57498c3eb495360944a33571754241e15e47e3bcae6082f40fec5866" + +[[package]] +name = "cranelift-entity" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11aa8aa624c72cc1c94ea3d0739fa61248260b5b14d3646f51593a88d67f3e6e" + +[[package]] +name = "cranelift-frontend" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "544ee8f4d1c9559c9aa6d46e7aaeac4a13856d620561094f35527356c7d21bd0" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.86.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed16b14363d929b8c37e3c557d0a7396791b383ecc302141643c054343170aad" + [[package]] name = "crossbeam-channel" version = "0.5.4" @@ -157,14 +333,34 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core 0.14.2", + "darling_macro 0.14.2", ] [[package]] @@ -181,13 +377,37 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core 0.14.2", "quote", "syn", ] @@ -201,12 +421,89 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", +] + +[[package]] +name = "dynasm" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +dependencies = [ + "byteorder", + "dynasm", + "memmap2", +] + [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumset" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" +dependencies = [ + "darling 0.14.2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "eyre" version = "0.6.8" @@ -217,12 +514,27 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -233,12 +545,49 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" + [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -282,7 +631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -291,6 +640,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "keccak" version = "0.1.0" @@ -303,11 +661,45 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" -version = "0.2.125" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mach" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] [[package]] name = "memchr" @@ -315,6 +707,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memmap2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -330,6 +731,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + [[package]] name = "nom" version = "7.1.1" @@ -438,17 +854,55 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.10.0" +name = "object" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" +dependencies = [ + "memchr", +] [[package]] -name = "opaque-debug" +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.42.0", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -486,27 +940,54 @@ dependencies = [ name = "prover" version = "0.1.0" dependencies = [ + "arbutil", "bincode", "brotli2", - "digest", + "digest 0.9.0", "eyre", "fnv", "hex", + "lazy_static", "libc", "nom", "nom-leb128", "num", + "parking_lot", "rayon", "rustc-demangle", "serde", "serde_json", "serde_with", - "sha3", + "sha3 0.9.1", + "smallvec", "static_assertions", "structopt", + "wasmer", + "wasmer-compiler-singlepass", + "wasmer-types", "wasmparser", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.18" @@ -540,6 +1021,74 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regalloc2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "region" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +dependencies = [ + "bytecheck", + "hashbrown 0.12.3", + "indexmap", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -564,6 +1113,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "serde" version = "1.0.137" @@ -573,6 +1128,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.137" @@ -612,7 +1178,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "syn", @@ -624,12 +1190,49 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "keccak", "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.6", + "keccak", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +dependencies = [ + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -683,6 +1286,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "target-lexicon" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" + [[package]] name = "textwrap" version = "0.11.0" @@ -692,6 +1301,58 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "typenum" version = "1.15.0" @@ -728,10 +1389,103 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-downcast" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dac026d43bcca6e7ce1c0956ba68f59edf6403e8e930a5d891be72c31a44340" +dependencies = [ + "js-sys", + "once_cell", + "wasm-bindgen", + "wasm-bindgen-downcast-macros", +] + +[[package]] +name = "wasm-bindgen-downcast-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5020cfa87c7cecefef118055d44e3c1fc122c7ec25701d528ee458a0b45f38f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wasm-encoder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-testsuite" version = "0.1.0" dependencies = [ + "arbutil", "eyre", "hex", "prover", @@ -740,13 +1494,156 @@ dependencies = [ "structopt", ] +[[package]] +name = "wasmer" +version = "3.1.0" +dependencies = [ + "bytes", + "cfg-if", + "indexmap", + "js-sys", + "more-asserts", + "serde", + "serde-wasm-bindgen", + "target-lexicon", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-downcast", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-derive", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi", +] + +[[package]] +name = "wasmer-compiler" +version = "3.1.0" +dependencies = [ + "backtrace", + "cfg-if", + "enum-iterator", + "enumset", + "lazy_static", + "leb128", + "memmap2", + "more-asserts", + "region", + "rustc-demangle", + "smallvec", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser", + "winapi", +] + +[[package]] +name = "wasmer-compiler-cranelift" +version = "3.1.0" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "gimli 0.26.2", + "more-asserts", + "rayon", + "smallvec", + "target-lexicon", + "tracing", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-compiler-singlepass" +version = "3.1.0" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "enumset", + "gimli 0.26.2", + "lazy_static", + "more-asserts", + "rayon", + "smallvec", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-derive" +version = "3.1.0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmer-types" +version = "3.1.0" +dependencies = [ + "enum-iterator", + "enumset", + "indexmap", + "more-asserts", + "rkyv", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "wasmer-vm" +version = "3.1.0" +dependencies = [ + "backtrace", + "cc", + "cfg-if", + "corosensei", + "enum-iterator", + "indexmap", + "lazy_static", + "libc", + "mach", + "memoffset", + "more-asserts", + "region", + "scopeguard", + "thiserror", + "wasmer-types", + "winapi", +] + [[package]] name = "wasmparser" -version = "0.84.0" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77dc97c22bb5ce49a47b745bed8812d30206eff5ef3af31424f2c1820c0974b2" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" + +[[package]] +name = "wast" +version = "50.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" dependencies = [ - "indexmap", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" +dependencies = [ + "wast", ] [[package]] @@ -770,3 +1667,103 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/arbitrator/wasm-testsuite/Cargo.toml b/arbitrator/wasm-testsuite/Cargo.toml index 5ace2ca58..b24570ab5 100644 --- a/arbitrator/wasm-testsuite/Cargo.toml +++ b/arbitrator/wasm-testsuite/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +arbutil = { path = "../arbutil" } prover = { path = "../prover" } structopt = "0.3.23" serde = { version = "1.0.130", features = ["derive", "rc"] } diff --git a/arbitrator/wasm-testsuite/check.sh b/arbitrator/wasm-testsuite/check.sh index 9c67557dc..a32e08465 100755 --- a/arbitrator/wasm-testsuite/check.sh +++ b/arbitrator/wasm-testsuite/check.sh @@ -18,7 +18,7 @@ cargo build --release for file in tests/*.json; do base="${file#tests/}" name="${base%.wasm}" - target/release/wasm-testsuite $name & + nice target/release/wasm-testsuite $name & done wait diff --git a/arbitrator/wasm-testsuite/src/main.rs b/arbitrator/wasm-testsuite/src/main.rs index 4ff511d9d..2144dcf99 100644 --- a/arbitrator/wasm-testsuite/src/main.rs +++ b/arbitrator/wasm-testsuite/src/main.rs @@ -1,9 +1,9 @@ // Copyright 2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use eyre::bail; +use arbutil::Color; +use eyre::{bail, ErrReport}; use prover::{ - console::Color, machine, machine::{GlobalState, Machine, MachineStatus, ProofInfo}, value::Value, @@ -11,6 +11,7 @@ use prover::{ use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, + convert::TryInto, fs::File, io::BufReader, path::PathBuf, @@ -54,6 +55,7 @@ enum Command { }, AssertInvalid {}, AssertUninstantiable {}, + Register {}, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -67,7 +69,7 @@ enum Action { struct TextValue { #[serde(rename = "type")] ty: TextValueType, - value: String, + value: TextValueData, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -77,52 +79,74 @@ enum TextValueType { I64, F32, F64, + V128, + Funcref, + Externref, } -impl Into for TextValue { - fn into(self) -> Value { - match self.ty { - TextValueType::I32 => { - let value = self.value.parse().expect("not an i32"); +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +enum TextValueData { + String(String), + Array(Vec), +} + +impl TryInto for TextValue { + type Error = ErrReport; + + fn try_into(self) -> eyre::Result { + let TextValueData::String(value) = self.value else { + bail!("array-expressed values not supported"); + }; + + use TextValueType::*; + Ok(match self.ty { + I32 => { + let value = value.parse().expect("not an i32"); Value::I32(value) } - TextValueType::I64 => { - let value = self.value.parse().expect("not an i64"); + I64 => { + let value = value.parse().expect("not an i64"); Value::I64(value) } - TextValueType::F32 => { - if self.value.contains("nan") { - return Value::F32(f32::NAN); + F32 => { + if value.contains("nan") { + return Ok(Value::F32(f32::NAN)); } - let message = format!("{} not the bit representation of an f32", self.value); - let bits: u32 = self.value.parse().expect(&message); + let message = format!("{} not the bit representation of an f32", value); + let bits: u32 = value.parse().expect(&message); Value::F32(f32::from_bits(bits)) } - TextValueType::F64 => { - if self.value.contains("nan") { - return Value::F64(f64::NAN); + F64 => { + if value.contains("nan") { + return Ok(Value::F64(f64::NAN)); } - let message = format!("{} not the bit representation of an f64", self.value); - let bits: u64 = self.value.parse().expect(&message); + let message = format!("{} not the bit representation of an f64", value); + let bits: u64 = value.parse().expect(&message); Value::F64(f64::from_bits(bits)) } - } + x @ (V128 | Funcref | Externref) => bail!("not supported {:?}", x), + }) } } impl PartialEq for TextValue { fn eq(&self, other: &Value) -> bool { - if &Into::::into(self.clone()) == other { + if &TryInto::::try_into(self.clone()).unwrap() == other { return true; } + let TextValueData::String(text_value) = &self.value else { + panic!("array-expressed values not supported"); + }; + match self.ty { TextValueType::F32 => match other { - Value::F32(value) => value.is_nan() && self.value.contains("nan"), + Value::F32(value) => value.is_nan() && text_value.contains("nan"), _ => false, }, TextValueType::F64 => match other { - Value::F64(value) => value.is_nan() && self.value.contains("nan"), + Value::F64(value) => value.is_nan() && text_value.contains("nan"), _ => false, }, _ => false, @@ -166,13 +190,33 @@ fn main() -> eyre::Result<()> { do_not_prove.insert(PathBuf::from("float_exprs.json")); let export_proofs = !do_not_prove.contains(&opts.json); if !export_proofs { - println!("{}", Color::grey("skipping OSP proof generation")); + println!("{}", "skipping OSP proof generation".grey()); + } + + fn setup<'a>( + machine: &'a mut Option, + func: &str, + args: Vec, + file: &str, + ) -> &'a mut Machine { + let Some(machine) = machine.as_mut() else { + panic!("no machine {} {}", file.red(), func.red()) + }; + let main = machine.main_module_name(); + let (module, func) = machine.find_module_func(&main, func).unwrap(); + machine.jump_into_func(module, func, args); + machine + } + + fn to_values(text: Vec) -> eyre::Result> { + text.into_iter().map(TryInto::try_into).collect() } let mut wasmfile = String::new(); let mut machine = None; let mut subtest = 0; let mut skip = false; + let mut has_skipped = false; macro_rules! run { ($machine:expr, $bound:expr, $path:expr, $prove:expr) => {{ @@ -199,7 +243,7 @@ fn main() -> eyre::Result<()> { leap *= leap + 1; if leap > 6 { let message = format!("backing off {} {} {}", leap, count, $bound); - println!("{}", Color::grey(message)); + println!("{}", message.grey()); $machine.stop_merkle_caching(); } } @@ -219,7 +263,7 @@ fn main() -> eyre::Result<()> { Action::Invoke { field, args } => (field, args), Action::Get { .. } => { // get() is only used in the export test, which we don't support - println!("skipping unsupported action {}", Color::red("get")); + println!("skipping unsupported action {}", "get".red()); continue; } } @@ -234,30 +278,33 @@ fn main() -> eyre::Result<()> { }; } - for (index, command) in case.commands.into_iter().enumerate() { + 'next: for (index, command) in case.commands.into_iter().enumerate() { + // each iteration represets a test case + macro_rules! test_success { ($func:expr, $args:expr, $expected:expr) => { - let args: Vec<_> = $args.into_iter().map(Into::into).collect(); + let args = match to_values($args) { + Ok(args) => args, + Err(_) => continue, // TODO: can't use let-else due to rust fmt bug + }; if skip { - println!("skipping {}", Color::red($func)); + if !has_skipped { + println!("skipping {}", $func.red()); + } subtest += 1; + has_skipped = true; continue; } - let machine = machine.as_mut().expect("no machine"); - machine.jump_into_function(&$func, args.clone()); + let machine = setup(&mut machine, &$func, args.clone(), &wasmfile); machine.start_merkle_caching(); run!(machine, 10_000_000, outname!(), true); let output = match machine.get_final_result() { Ok(output) => output, Err(error) => { - let expected: Vec = $expected.into_iter().map(Into::into).collect(); - println!( - "Divergence in func {} of test {}", - Color::red($func), - Color::red(index), - ); + let expected = to_values($expected)?; + println!("Divergence in func {} of test {}", $func.red(), index.red()); pretty_print_values("Args ", args); pretty_print_values("Expected", expected); println!(); @@ -266,19 +313,15 @@ fn main() -> eyre::Result<()> { }; if $expected != output { - let expected: Vec = $expected.into_iter().map(Into::into).collect(); - println!( - "Divergence in func {} of test {}", - Color::red($func), - Color::red(index), - ); + let expected = to_values($expected)?; + println!("Divergence in func {} of test {}", $func.red(), index.red()); pretty_print_values("Args ", args); pretty_print_values("Expected", expected); pretty_print_values("Observed", output); println!(); bail!( "Failure in test {}", - Color::red(format!("{} #{}", wasmfile, subtest)) + format!("{} #{}", wasmfile, subtest).red() ) } subtest += 1; @@ -306,21 +349,20 @@ fn main() -> eyre::Result<()> { let error = error.root_cause().to_string(); skip = true; - if error.contains("Module has no code") { - // We don't support metadata-only modules that have no code - continue; - } - if error.contains("Unsupported import") { - // We don't support the import test's functions - continue; - } - if error.contains("multiple tables") { - // We don't support the reference-type extension - continue; - } - if error.contains("bulk memory") { - // We don't support the bulk-memory extension - continue; + let skippables = vec![ + "module has no code", // we don't support metadata-only modules that have no code + "no such import", // we don't support imports + "unsupported import", // we don't support imports + "reference types", // we don't support the reference-type extension + "multiple tables", // we don't support the reference-type extension + "bulk memory", // we don't support the bulk-memory extension + "simd support", // we don't support the SIMD extension + ]; + + for skippable in skippables { + if error.to_lowercase().contains(skippable) { + continue 'next; + } } bail!("Unexpected error parsing module {}: {}", wasmfile, error) } @@ -344,22 +386,17 @@ fn main() -> eyre::Result<()> { } Command::AssertTrap { action } => { let (func, args) = action!(action); - let args: Vec<_> = args.into_iter().map(Into::into).collect(); - let test = Color::red(format!("{} #{}", wasmfile, subtest)); + let args = to_values(args)?; + let test = format!("{} #{}", wasmfile, subtest).red(); - let machine = machine.as_mut().unwrap(); - machine.jump_into_function(&func, args.clone()); + let machine = setup(&mut machine, &func, args.clone(), &wasmfile); run!(machine, 1000, outname!(), true); if machine.get_status() == MachineStatus::Running { bail!("machine failed to trap in test {}", test) } if let Ok(output) = machine.get_final_result() { - println!( - "Divergence in func {} of test {}", - Color::red(func), - Color::red(index), - ); + println!("Divergence in func {} of test {}", func.red(), index.red()); pretty_print_values("Args ", args); pretty_print_values("Output", output); println!(); @@ -369,11 +406,10 @@ fn main() -> eyre::Result<()> { } Command::AssertExhaustion { action } => { let (func, args) = action!(action); - let args: Vec<_> = args.into_iter().map(Into::into).collect(); - let test = Color::red(format!("{} #{}", wasmfile, subtest)); + let args = to_values(args)?; + let test = format!("{} #{}", wasmfile, subtest).red(); - let machine = machine.as_mut().unwrap(); - machine.jump_into_function(&func, args.clone()); + let machine = setup(&mut machine, &func, args.clone(), &wasmfile); run!(machine, 100_000, outname!(), false); // this is proportional to the amount of RAM if machine.get_status() != MachineStatus::Running { @@ -402,8 +438,8 @@ fn main() -> eyre::Result<()> { println!( "{} {}", - Color::grey("done in"), - Color::pink(format!("{}ms", start_time.elapsed().as_millis())) + "done in".grey(), + format!("{}ms", start_time.elapsed().as_millis()).pink() ); Ok(()) } diff --git a/arbnode/api.go b/arbnode/api.go index 51437864d..228ad51cf 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -2,7 +2,6 @@ package arbnode import ( "context" - "errors" "fmt" "time" @@ -40,11 +39,11 @@ func (a *BlockValidatorDebugAPI) ValidateMessageNumber( if moduleRootOptional != nil { moduleRoot = *moduleRootOptional } else { - moduleRoots := a.val.GetModuleRootsToValidate() - if len(moduleRoots) == 0 { - return result, errors.New("no current WasmModuleRoot configured, must provide parameter") + var err error + moduleRoot, err = a.val.GetLatestWasmModuleRoot(ctx) + if err != nil { + return result, fmt.Errorf("no latest WasmModuleRoot configured, must provide parameter: %w", err) } - moduleRoot = moduleRoots[0] } start_time := time.Now() valid, gs, err := a.val.ValidateResult(ctx, arbutil.MessageIndex(msgNum), full, moduleRoot) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 3ed816415..0e0cc13d1 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -38,11 +38,10 @@ import ( "github.com/offchainlabs/nitro/arbnode/redislock" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/genericconf" - "github.com/offchainlabs/nitro/das" - "github.com/offchainlabs/nitro/eigenda" "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util" @@ -54,8 +53,24 @@ import ( ) var ( - batchPosterWalletBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/wallet/balanceether", nil) - batchPosterGasRefunderBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/gasrefunder/balanceether", nil) + batchPosterWalletBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/wallet/eth", nil) + batchPosterGasRefunderBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/gasrefunder/eth", nil) + baseFeeGauge = metrics.NewRegisteredGauge("arb/batchposter/basefee", nil) + blobFeeGauge = metrics.NewRegisteredGauge("arb/batchposter/blobfee", nil) + l1GasPriceGauge = metrics.NewRegisteredGauge("arb/batchposter/l1gasprice", nil) + blockGasUsedGauge = metrics.NewRegisteredGauge("arb/batchposter/blockgas/used", nil) + blockGasLimitGauge = metrics.NewRegisteredGauge("arb/batchposter/blockgas/limit", nil) + blobGasUsedGauge = metrics.NewRegisteredGauge("arb/batchposter/blobgas/used", nil) + blobGasLimitGauge = metrics.NewRegisteredGauge("arb/batchposter/blobgas/limit", nil) + suggestedTipCapGauge = metrics.NewRegisteredGauge("arb/batchposter/suggestedtipcap", nil) + + batchPosterEstimatedBatchBacklogGauge = metrics.NewRegisteredGauge("arb/batchposter/estimated_batch_backlog", nil) + + batchPosterDALastSuccessfulActionGauge = metrics.NewRegisteredGauge("arb/batchPoster/action/da_last_success", nil) + batchPosterDASuccessCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_success", nil) + batchPosterDAFailureCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_failure", nil) + + batchPosterFailureCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/failure", nil) usableBytesInBlob = big.NewInt(int64(len(kzg4844.Blob{}) * 31 / 32)) blobTxBlobGasPerBlob = big.NewInt(params.BlobTxBlobGasPerBlob) @@ -90,8 +105,7 @@ type BatchPoster struct { bridgeAddr common.Address gasRefunderAddr common.Address building *buildingBatch - daWriter das.DataAvailabilityServiceWriter - eigenDAWriter eigenda.EigenDAWriter + dapWriter daprovider.Writer dataPoster *dataposter.DataPoster redisLock *redislock.Simple messagesPerBatch *arbmath.MovingAverage[uint64] @@ -104,6 +118,7 @@ type BatchPoster struct { batchReverted atomic.Bool // indicates whether data poster batch was reverted nextRevertCheckBlock int64 // the last parent block scanned for reverting batches + postedFirstBatch bool // indicates if batch poster has posted the first batch accessList func(SequencerInboxAccs, AfterDelayedMessagesRead int) types.AccessList } @@ -120,10 +135,13 @@ const ( l1BlockBoundIgnore ) +type BatchPosterDangerousConfig struct { + AllowPostingFirstBatchWhenSequencerMessageCountMismatch bool `koanf:"allow-posting-first-batch-when-sequencer-message-count-mismatch"` +} + type BatchPosterConfig struct { - Enable bool `koanf:"enable"` - DisableDasFallbackStoreDataOnChain bool `koanf:"disable-das-fallback-store-data-on-chain" reload:"hot"` - DisableEigenDAFallbackStoreDataOnChain bool `koanf:"disable-eigenda-fallback-store-data-on-chain" reload:"hot"` + Enable bool `koanf:"enable"` + DisableDapFallbackStoreDataOnChain bool `koanf:"disable-dap-fallback-store-data-on-chain" reload:"hot"` // Max batch size. MaxSize int `koanf:"max-size" reload:"hot"` // Maximum 4844 blob enabled batch size. @@ -151,6 +169,7 @@ type BatchPosterConfig struct { L1BlockBoundBypass time.Duration `koanf:"l1-block-bound-bypass" reload:"hot"` UseAccessLists bool `koanf:"use-access-lists" reload:"hot"` GasEstimateBaseFeeMultipleBips arbmath.Bips `koanf:"gas-estimate-base-fee-multiple-bips"` + Dangerous BatchPosterDangerousConfig `koanf:"dangerous"` gasRefunder common.Address l1BlockBound l1BlockBound @@ -184,7 +203,7 @@ type BatchPosterConfigFetcher func() *BatchPosterConfig func BatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBatchPosterConfig.Enable, "enable posting batches to l1") - f.Bool(prefix+".disable-das-fallback-store-data-on-chain", DefaultBatchPosterConfig.DisableDasFallbackStoreDataOnChain, "If unable to batch to DAS, disable fallback storing data on chain") + f.Bool(prefix+".disable-dap-fallback-store-data-on-chain", DefaultBatchPosterConfig.DisableDapFallbackStoreDataOnChain, "If unable to batch to DA provider, disable fallback storing data on chain") f.Int(prefix+".max-size", DefaultBatchPosterConfig.MaxSize, "maximum batch size") f.Int(prefix+".max-4844-batch-size", DefaultBatchPosterConfig.Max4844BatchSize, "maximum 4844 blob enabled batch size") f.Duration(prefix+".max-delay", DefaultBatchPosterConfig.MaxDelay, "maximum batch posting delay") @@ -210,11 +229,11 @@ func BatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { var DefaultBatchPosterConfig = BatchPosterConfig{ Enable: false, - DisableDasFallbackStoreDataOnChain: false, + DisableDapFallbackStoreDataOnChain: false, // This default is overridden for L3 chains in applyChainParameters in cmd/nitro/nitro.go MaxSize: 100000, - // TODO: is 1000 bytes an appropriate margin for error vs blob space efficiency? - Max4844BatchSize: blobs.BlobEncodableData*(params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) - 1000, + // Try to fill 3 blobs per batch + Max4844BatchSize: blobs.BlobEncodableData*(params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob)/2 - 2000, PollInterval: time.Second * 10, ErrorDelay: time.Second * 10, MaxDelay: time.Hour, @@ -299,7 +318,7 @@ type BatchPosterOpts struct { Config BatchPosterConfigFetcher DeployInfo *chaininfo.RollupAddresses TransactOpts *bind.TransactOpts - DAWriter das.DataAvailabilityServiceWriter + DAPWriter daprovider.Writer ParentChainID *big.Int EigenDAWriter eigenda.EigenDAWriter } @@ -334,20 +353,20 @@ func NewBatchPoster(ctx context.Context, opts *BatchPosterOpts) (*BatchPoster, e return nil, err } b := &BatchPoster{ - l1Reader: opts.L1Reader, - inbox: opts.Inbox, - streamer: opts.Streamer, - syncMonitor: opts.SyncMonitor, - config: opts.Config, - bridge: bridge, - seqInbox: seqInbox, - seqInboxABI: seqInboxABI, - seqInboxAddr: opts.DeployInfo.SequencerInbox, - gasRefunderAddr: opts.Config().gasRefunder, - bridgeAddr: opts.DeployInfo.Bridge, - daWriter: opts.DAWriter, - eigenDAWriter: opts.EigenDAWriter, - redisLock: redisLock, + l1Reader: opts.L1Reader, + inbox: opts.Inbox, + streamer: opts.Streamer, + arbOSVersionGetter: opts.VersionGetter, + syncMonitor: opts.SyncMonitor, + config: opts.Config, + bridge: bridge, + seqInbox: seqInbox, + seqInboxABI: seqInboxABI, + seqInboxAddr: opts.DeployInfo.SequencerInbox, + gasRefunderAddr: opts.Config().gasRefunder, + bridgeAddr: opts.DeployInfo.Bridge, + dapWriter: opts.DAPWriter, + redisLock: redisLock, } b.messagesPerBatch, err = arbmath.NewMovingAverage[uint64](20) if err != nil { @@ -513,7 +532,7 @@ func (b *BatchPoster) checkReverts(ctx context.Context, to int64) (bool, error) return false, fmt.Errorf("getting a receipt for transaction: %v, %w", tx.Hash, err) } if r.Status == types.ReceiptStatusFailed { - shouldHalt := !b.config().DataPoster.UseNoOpStorage + shouldHalt := !b.dataPoster.UsingNoOpStorage() logLevel := log.Warn if shouldHalt { logLevel = log.Error @@ -542,6 +561,47 @@ func (b *BatchPoster) checkReverts(ctx context.Context, to int64) (bool, error) return false, nil } +func (b *BatchPoster) pollForL1PriceData(ctx context.Context) { + headerCh, unsubscribe := b.l1Reader.Subscribe(false) + defer unsubscribe() + + blobGasLimitGauge.Update(params.MaxBlobGasPerBlock) + for { + select { + case h, ok := <-headerCh: + if !ok { + log.Info("L1 headers channel checking for l1 price data has been closed") + return + } + baseFeeGauge.Update(h.BaseFee.Int64()) + l1GasPrice := h.BaseFee.Uint64() + if h.BlobGasUsed != nil { + if h.ExcessBlobGas != nil { + blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*h.ExcessBlobGas, *h.BlobGasUsed)) + blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) + blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) + blobFeeGauge.Update(blobFeePerByte.Int64()) + if l1GasPrice > blobFeePerByte.Uint64()/16 { + l1GasPrice = blobFeePerByte.Uint64() / 16 + } + } + blobGasUsedGauge.Update(int64(*h.BlobGasUsed)) + } + blockGasUsedGauge.Update(int64(h.GasUsed)) + blockGasLimitGauge.Update(int64(h.GasLimit)) + suggestedTipCap, err := b.l1Reader.Client().SuggestGasTipCap(ctx) + if err != nil { + log.Warn("unable to fetch suggestedTipCap from l1 client to update arb/batchposter/suggestedtipcap metric", "err", err) + } else { + suggestedTipCapGauge.Update(suggestedTipCap.Int64()) + } + l1GasPriceGauge.Update(int64(l1GasPrice)) + case <-ctx.Done(): + return + } + } +} + // pollForReverts runs a gouroutine that listens to l1 block headers, checks // if any transaction made by batch poster was reverted. func (b *BatchPoster) pollForReverts(ctx context.Context) { @@ -864,7 +924,7 @@ func (s *batchSegments) CloseAndGetBytes() ([]byte, error) { } compressedBytes := s.compressedBuffer.Bytes() fullMsg := make([]byte, 1, len(compressedBytes)+1) - fullMsg[0] = arbstate.BrotliMessageHeaderByte + fullMsg[0] = daprovider.BrotliMessageHeaderByte fullMsg = append(fullMsg, compressedBytes...) return fullMsg, nil } @@ -1132,7 +1192,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } var use4844 bool config := b.config() - if config.Post4844Blobs && b.daWriter == nil && latestHeader.ExcessBlobGas != nil && latestHeader.BlobGasUsed != nil { + if config.Post4844Blobs && b.dapWriter == nil && latestHeader.ExcessBlobGas != nil && latestHeader.BlobGasUsed != nil { arbOSVersion, err := b.arbOSVersionGetter.ArbOSVersionForMessageNumber(arbutil.MessageIndex(arbmath.SaturatingUSub(uint64(batchPosition.MessageCount), 1))) if err != nil { return false, err @@ -1196,7 +1256,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } config := b.config() - forcePostBatch := time.Since(firstMsgTime) >= config.MaxDelay + forcePostBatch := config.MaxDelay <= 0 || time.Since(firstMsgTime) >= config.MaxDelay var l1BoundMaxBlockNumber uint64 = math.MaxUint64 var l1BoundMaxTimestamp uint64 = math.MaxUint64 @@ -1313,6 +1373,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) // don't post anything for now return false, nil } + sequencerMsg, err := b.building.segments.CloseAndGetBytes() if err != nil { return false, err @@ -1323,47 +1384,66 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) return false, nil } - if b.daWriter != nil { + if b.dapWriter != nil { if !b.redisLock.AttemptLock(ctx) { return false, errAttemptLockFailed } gotNonce, gotMeta, err := b.dataPoster.GetNextNonceAndMeta(ctx) if err != nil { + batchPosterDAFailureCounter.Inc(1) return false, err } if nonce != gotNonce || !bytes.Equal(batchPositionBytes, gotMeta) { + batchPosterDAFailureCounter.Inc(1) return false, fmt.Errorf("%w: nonce changed from %d to %d while creating batch", storage.ErrStorageRace, nonce, gotNonce) } - - cert, err := b.daWriter.Store(ctx, sequencerMsg, uint64(time.Now().Add(config.DASRetentionPeriod).Unix()), []byte{}) // b.daWriter will append signature if enabled - if errors.Is(err, das.BatchToDasFailed) { - if config.DisableDasFallbackStoreDataOnChain { - return false, errors.New("unable to batch to DAS and fallback storing data on chain is disabled") - } - log.Warn("Falling back to storing data on chain", "err", err) - } else if err != nil { - return false, err - } else { - sequencerMsg = das.Serialize(cert) - } - } - - var blobInfo *eigenda.EigenDABlobInfo - if b.daWriter == nil && b.eigenDAWriter != nil { - log.Info("Start to write data to eigenda: ", "data", hex.EncodeToString(sequencerMsg)) - blobInfo, err = b.eigenDAWriter.Store(ctx, sequencerMsg) + sequencerMsg, err = b.dapWriter.Store(ctx, sequencerMsg, uint64(time.Now().Add(config.DASRetentionPeriod).Unix()), config.DisableDapFallbackStoreDataOnChain) if err != nil { + batchPosterDAFailureCounter.Inc(1) return false, err } - } - data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, b.building.use4844, b.building.useEigenDA, blobInfo) + batchPosterDASuccessCounter.Inc(1) + batchPosterDALastSuccessfulActionGauge.Update(time.Now().Unix()) + } + + prevMessageCount := batchPosition.MessageCount + if b.config().Dangerous.AllowPostingFirstBatchWhenSequencerMessageCountMismatch && !b.postedFirstBatch { + // AllowPostingFirstBatchWhenSequencerMessageCountMismatch can be used when the + // message count stored in batch poster's database gets out + // of sync with the sequencerReportedSubMessageCount stored in the parent chain. + // + // An example of when this out of sync issue can happen: + // 1. Batch poster is running fine, but then it shutdowns for more than 24h. + // 2. While the batch poster is down, someone sends a transaction to the parent chain + // smart contract to move a message from the delayed inbox to the main inbox. + // This will not update sequencerReportedSubMessageCount in the parent chain. + // 3. When batch poster starts again, the inbox reader will update the + // message count that is maintained in the batch poster's database to be equal to + // (sequencerReportedSubMessageCount that is stored in parent chain) + + // (the amount of delayed messages that were moved from the delayed inbox to the main inbox). + // At this moment the message count stored on batch poster's database gets out of sync with + // the sequencerReportedSubMessageCount stored in the parent chain. + + // When the first batch is posted, sequencerReportedSubMessageCount in + // the parent chain will be updated to be equal to the new message count provided + // by the batch poster, which will make this out of sync issue disappear. + // That is why this strategy is only applied for the first batch posted after + // startup. + + // If prevMessageCount is set to zero, sequencer inbox's smart contract allows + // to post a batch even if sequencerReportedSubMessageCount is not equal + // to the provided prevMessageCount + prevMessageCount = 0 + } + + data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), prevMessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, b.building.use4844) if err != nil { return false, err } if len(kzgBlobs)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { - return false, fmt.Errorf("produced %v blobs for batch but a block can only hold %v", len(kzgBlobs), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) + return false, fmt.Errorf("produced %v blobs for batch but a block can only hold %v (compressed batch was %v bytes long)", len(kzgBlobs), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob, len(sequencerMsg)) } accessList := b.accessList(int(batchPosition.NextSeqNum), int(b.building.segments.delayedMsg)) // On restart, we may be trying to estimate gas for a batch whose successor has @@ -1399,6 +1479,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) if err != nil { return false, err } + b.postedFirstBatch = true log.Info( "BatchPoster: batch sent", "eigenDA", b.building.useEigenDA, @@ -1433,6 +1514,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) messagesPerBatch = 1 } backlog := uint64(unpostedMessages) / messagesPerBatch + batchPosterEstimatedBatchBacklogGauge.Update(int64(backlog)) if backlog > 10 { logLevel := log.Warn if recentlyHitL1Bounds { @@ -1480,6 +1562,7 @@ func (b *BatchPoster) Start(ctxIn context.Context) { b.redisLock.Start(ctxIn) b.StopWaiter.Start(ctxIn, b) b.LaunchThread(b.pollForReverts) + b.LaunchThread(b.pollForL1PriceData) commonEphemeralErrorHandler := util.NewEphemeralErrorHandler(time.Minute, "", 0) exceedMaxMempoolSizeEphemeralErrorHandler := util.NewEphemeralErrorHandler(5*time.Minute, dataposter.ErrExceedsMaxMempoolSize.Error(), time.Minute) storageRaceEphemeralErrorHandler := util.NewEphemeralErrorHandler(5*time.Minute, storage.ErrStorageRace.Error(), time.Minute) @@ -1544,6 +1627,7 @@ func (b *BatchPoster) Start(ctxIn context.Context) { logLevel = normalGasEstimationFailedEphemeralErrorHandler.LogLevel(err, logLevel) logLevel = accumulatorNotFoundEphemeralErrorHandler.LogLevel(err, logLevel) logLevel("error posting batch", "err", err) + batchPosterFailureCounter.Inc(1) return b.config().ErrorDelay } else if posted { return 0 diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 3214e2fb2..4da3ea539 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -31,10 +31,10 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/go-redis/redis/v8" "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbnode/dataposter/dbstorage" - "github.com/offchainlabs/nitro/arbnode/dataposter/externalsigner" "github.com/offchainlabs/nitro/arbnode/dataposter/noop" "github.com/offchainlabs/nitro/arbnode/dataposter/slice" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" @@ -217,6 +217,10 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro func rpcClient(ctx context.Context, opts *ExternalSignerCfg) (*rpc.Client, error) { tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, + // Dataposter verifies that signed transaction was signed by the account + // that it expects to be signed with. So signer is already authenticated + // on application level and does not need to rely on TLS for authentication. + InsecureSkipVerify: opts.InsecureSkipVerify, // #nosec G402 } if opts.ClientCert != "" && opts.ClientPrivateKey != "" { @@ -251,6 +255,50 @@ func rpcClient(ctx context.Context, opts *ExternalSignerCfg) (*rpc.Client, error ) } +// TxToSignTxArgs converts transaction to SendTxArgs. This is needed for +// external signer to specify From field. +func TxToSignTxArgs(addr common.Address, tx *types.Transaction) (*apitypes.SendTxArgs, error) { + var to *common.MixedcaseAddress + if tx.To() != nil { + to = new(common.MixedcaseAddress) + *to = common.NewMixedcaseAddress(*tx.To()) + } + data := (hexutil.Bytes)(tx.Data()) + val := (*hexutil.Big)(tx.Value()) + if val == nil { + val = (*hexutil.Big)(big.NewInt(0)) + } + al := tx.AccessList() + var ( + blobs []kzg4844.Blob + commitments []kzg4844.Commitment + proofs []kzg4844.Proof + ) + if tx.BlobTxSidecar() != nil { + blobs = tx.BlobTxSidecar().Blobs + commitments = tx.BlobTxSidecar().Commitments + proofs = tx.BlobTxSidecar().Proofs + } + return &apitypes.SendTxArgs{ + From: common.NewMixedcaseAddress(addr), + To: to, + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + MaxFeePerGas: (*hexutil.Big)(tx.GasFeeCap()), + MaxPriorityFeePerGas: (*hexutil.Big)(tx.GasTipCap()), + Value: *val, + Nonce: hexutil.Uint64(tx.Nonce()), + Data: &data, + AccessList: &al, + ChainID: (*hexutil.Big)(tx.ChainId()), + BlobFeeCap: (*hexutil.Big)(tx.BlobGasFeeCap()), + BlobHashes: tx.BlobHashes(), + Blobs: blobs, + Commitments: commitments, + Proofs: proofs, + }, nil +} + // externalSigner returns signer function and ethereum address of the signer. // Returns an error if address isn't specified or if it can't connect to the // signer RPC server. @@ -269,7 +317,7 @@ func externalSigner(ctx context.Context, opts *ExternalSignerCfg) (signerFn, com // RLP encoded transaction object. // https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_signtransaction var data hexutil.Bytes - args, err := externalsigner.TxToSignTxArgs(addr, tx) + args, err := TxToSignTxArgs(addr, tx) if err != nil { return nil, fmt.Errorf("error converting transaction to sendTxArgs: %w", err) } @@ -281,7 +329,11 @@ func externalSigner(ctx context.Context, opts *ExternalSignerCfg) (signerFn, com return nil, fmt.Errorf("unmarshaling signed transaction: %w", err) } hasher := types.LatestSignerForChainID(tx.ChainId()) - if h := hasher.Hash(args.ToTransaction()); h != hasher.Hash(signedTx) { + gotTx, err := args.ToTransaction() + if err != nil { + return nil, fmt.Errorf("converting transaction arguments into transaction: %w", err) + } + if h := hasher.Hash(gotTx); h != hasher.Hash(signedTx) { return nil, fmt.Errorf("transaction: %x from external signer differs from request: %x", hasher.Hash(signedTx), h) } return signedTx, nil @@ -304,6 +356,10 @@ func (p *DataPoster) MaxMempoolTransactions() uint64 { return arbmath.MinInt(config.MaxMempoolTransactions, config.MaxMempoolWeight) } +func (p *DataPoster) UsingNoOpStorage() bool { + return p.usingNoOpStorage +} + var ErrExceedsMaxMempoolSize = errors.New("posting this transaction will exceed max mempool size") // Does basic check whether posting transaction with specified nonce would @@ -588,7 +644,7 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u targetBlobCost := arbmath.BigMulByUint(newBlobFeeCap, blobGasUsed) targetNonBlobCost := arbmath.BigSub(targetMaxCost, targetBlobCost) newBaseFeeCap := arbmath.BigDivByUint(targetNonBlobCost, gasLimit) - if lastTx != nil && numBlobs > 0 && arbmath.BigDivToBips(newBaseFeeCap, lastTx.GasFeeCap()) < minRbfIncrease { + if lastTx != nil && numBlobs > 0 && lastTx.GasFeeCap().Sign() > 0 && arbmath.BigDivToBips(newBaseFeeCap, lastTx.GasFeeCap()) < minRbfIncrease { // Increase the non-blob fee cap to the minimum rbf increase newBaseFeeCap = arbmath.BigMulByBips(lastTx.GasFeeCap(), minRbfIncrease) newNonBlobCost := arbmath.BigMulByUint(newBaseFeeCap, gasLimit) @@ -661,6 +717,14 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u return lastTx.GasFeeCap(), lastTx.GasTipCap(), lastTx.BlobGasFeeCap(), nil } + // Ensure we bid at least 1 wei to prevent division by zero + if newBaseFeeCap.Sign() == 0 { + newBaseFeeCap = big.NewInt(1) + } + if newBlobFeeCap.Sign() == 0 { + newBlobFeeCap = big.NewInt(1) + } + return newBaseFeeCap, newTipCap, newBlobFeeCap, nil } @@ -672,6 +736,10 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim p.mutex.Lock() defer p.mutex.Unlock() + if p.config().DisableNewTx { + return nil, fmt.Errorf("posting new transaction is disabled") + } + var weight uint64 = 1 if len(kzgBlobs) > 0 { weight = uint64(len(kzgBlobs)) @@ -828,6 +896,41 @@ func (p *DataPoster) sendTx(ctx context.Context, prevTx *storage.QueuedTransacti if err := p.saveTx(ctx, prevTx, newTx); err != nil { return err } + + // The following check is to avoid sending transactions of a different type (eg DynamicFeeTxType vs BlobTxType) + // to the previous tx if the previous tx is not yet included in a reorg resistant block, in order to avoid issues + // where eventual consistency of parent chain mempools causes a tx with higher nonce blocking a tx of a + // different type with a lower nonce. + // If we decide not to send this tx yet, just leave it queued and with Sent set to false. + // The resending/repricing loop in DataPoster.Start will keep trying. + previouslySent := newTx.Sent || (prevTx != nil && prevTx.Sent) // if we've previously sent this nonce + if !previouslySent && newTx.FullTx.Nonce() > 0 { + precedingTx, err := p.queue.Get(ctx, arbmath.SaturatingUSub(newTx.FullTx.Nonce(), 1)) + if err != nil { + return fmt.Errorf("couldn't get preceding tx in DataPoster to check if should send tx with nonce %d: %w", newTx.FullTx.Nonce(), err) + } + if precedingTx != nil { // precedingTx == nil -> the actual preceding tx was already confirmed + var latestBlockNumber, prevBlockNumber, reorgResistantTxCount uint64 + if precedingTx.FullTx.Type() != newTx.FullTx.Type() || !precedingTx.Sent { + latestBlockNumber, err = p.client.BlockNumber(ctx) + if err != nil { + return fmt.Errorf("couldn't get block number in DataPoster to check if should send tx with nonce %d: %w", newTx.FullTx.Nonce(), err) + } + prevBlockNumber = arbmath.SaturatingUSub(latestBlockNumber, 1) + reorgResistantTxCount, err = p.client.NonceAt(ctx, p.Sender(), new(big.Int).SetUint64(prevBlockNumber)) + if err != nil { + return fmt.Errorf("couldn't determine reorg resistant nonce in DataPoster to check if should send tx with nonce %d: %w", newTx.FullTx.Nonce(), err) + } + + if newTx.FullTx.Nonce() > reorgResistantTxCount { + log.Info("DataPoster is avoiding creating a mempool nonce gap (the tx remains queued and will be retried)", "nonce", newTx.FullTx.Nonce(), "prevType", precedingTx.FullTx.Type(), "type", newTx.FullTx.Type(), "prevSent", precedingTx.Sent, "latestBlockNumber", latestBlockNumber, "prevBlockNumber", prevBlockNumber, "reorgResistantTxCount", reorgResistantTxCount) + return nil + } + } + log.Debug("DataPoster will send previously unsent batch tx", "nonce", newTx.FullTx.Nonce(), "prevType", precedingTx.FullTx.Type(), "type", newTx.FullTx.Type(), "prevSent", precedingTx.Sent, "latestBlockNumber", latestBlockNumber, "prevBlockNumber", prevBlockNumber, "reorgResistantTxCount", reorgResistantTxCount) + } + } + if err := p.client.SendTransaction(ctx, newTx.FullTx); err != nil { if !rpcclient.IsAlreadyKnownError(err) && !strings.Contains(err.Error(), "nonce too low") { log.Warn("DataPoster failed to send transaction", "err", err, "nonce", newTx.FullTx.Nonce(), "feeCap", newTx.FullTx.GasFeeCap(), "tipCap", newTx.FullTx.GasTipCap(), "blobFeeCap", newTx.FullTx.BlobGasFeeCap(), "gas", newTx.FullTx.Gas()) @@ -895,8 +998,8 @@ func (p *DataPoster) replaceTx(ctx context.Context, prevTx *storage.QueuedTransa } newTx := *prevTx - if arbmath.BigDivToBips(newFeeCap, prevTx.FullTx.GasFeeCap()) < minRbfIncrease || - (prevTx.FullTx.BlobGasFeeCap() != nil && arbmath.BigDivToBips(newBlobFeeCap, prevTx.FullTx.BlobGasFeeCap()) < minRbfIncrease) { + if (prevTx.FullTx.GasFeeCap().Sign() > 0 && arbmath.BigDivToBips(newFeeCap, prevTx.FullTx.GasFeeCap()) < minRbfIncrease) || + (prevTx.FullTx.BlobGasFeeCap() != nil && prevTx.FullTx.BlobGasFeeCap().Sign() > 0 && arbmath.BigDivToBips(newBlobFeeCap, prevTx.FullTx.BlobGasFeeCap()) < minRbfIncrease) { log.Debug( "no need to replace by fee transaction", "nonce", prevTx.FullTx.Nonce(), @@ -1069,19 +1172,21 @@ func (p *DataPoster) Start(ctxIn context.Context) { latestNonce = latestQueued.FullTx.Nonce() } for _, tx := range queueContents { - replacing := false + previouslyUnsent := !tx.Sent + sendAttempted := false if now.After(tx.NextReplacement) { - replacing = true nonceBacklog := arbmath.SaturatingUSub(latestNonce, tx.FullTx.Nonce()) weightBacklog := arbmath.SaturatingUSub(latestCumulativeWeight, tx.CumulativeWeight()) err := p.replaceTx(ctx, tx, arbmath.MaxInt(nonceBacklog, weightBacklog)) + sendAttempted = true p.maybeLogError(err, tx, "failed to replace-by-fee transaction") } if nextCheck.After(tx.NextReplacement) { nextCheck = tx.NextReplacement } - if !replacing && !tx.Sent { + if !sendAttempted && previouslyUnsent { err := p.sendTx(ctx, tx, tx) + sendAttempted = true p.maybeLogError(err, tx, "failed to re-send transaction") if err != nil { nextSend := time.Now().Add(time.Minute) @@ -1090,6 +1195,12 @@ func (p *DataPoster) Start(ctxIn context.Context) { } } } + if previouslyUnsent && sendAttempted { + // Don't try to send more than 1 unsent transaction, to play nicely with parent chain mempools. + // Transactions will be unsent if there was some error when originally sending them, + // or if transaction type changes and the prior tx is not yet reorg resistant. + break + } } wait := time.Until(nextCheck) if wait < minWait { @@ -1150,6 +1261,9 @@ type DataPosterConfig struct { MaxFeeCapFormula string `koanf:"max-fee-cap-formula" reload:"hot"` ElapsedTimeBase time.Duration `koanf:"elapsed-time-base" reload:"hot"` ElapsedTimeImportance float64 `koanf:"elapsed-time-importance" reload:"hot"` + // When set, dataposter will not post new batches, but will keep running to + // get existing batches confirmed. + DisableNewTx bool `koanf:"disable-new-tx" reload:"hot"` } type ExternalSignerCfg struct { @@ -1169,6 +1283,8 @@ type ExternalSignerCfg struct { // (Optional) Client certificate key for mtls. // This is required when client-cert is set. ClientPrivateKey string `koanf:"client-private-key"` + // TLS config option, when enabled skips certificate verification of external signer. + InsecureSkipVerify bool `koanf:"insecure-skip-verify"` } type DangerousConfig struct { @@ -1209,6 +1325,7 @@ func DataPosterConfigAddOptions(prefix string, f *pflag.FlagSet, defaultDataPost signature.SimpleHmacConfigAddOptions(prefix+".redis-signer", f) addDangerousOptions(prefix+".dangerous", f) addExternalSignerOptions(prefix+".external-signer", f) + f.Bool(prefix+".disable-new-tx", defaultDataPosterConfig.DisableNewTx, "disable posting new transactions, data poster will still keep confirming existing batches") } func addDangerousOptions(prefix string, f *pflag.FlagSet) { @@ -1222,6 +1339,7 @@ func addExternalSignerOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".root-ca", DefaultDataPosterConfig.ExternalSigner.RootCA, "external signer root CA") f.String(prefix+".client-cert", DefaultDataPosterConfig.ExternalSigner.ClientCert, "rpc client cert") f.String(prefix+".client-private-key", DefaultDataPosterConfig.ExternalSigner.ClientPrivateKey, "rpc client private key") + f.Bool(prefix+".insecure-skip-verify", DefaultDataPosterConfig.ExternalSigner.InsecureSkipVerify, "skip TLS certificate verification") } var DefaultDataPosterConfig = DataPosterConfig{ @@ -1243,10 +1361,11 @@ var DefaultDataPosterConfig = DataPosterConfig{ UseNoOpStorage: false, LegacyStorageEncoding: false, Dangerous: DangerousConfig{ClearDBStorage: false}, - ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction"}, + ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction", InsecureSkipVerify: false}, MaxFeeCapFormula: "((BacklogOfBatches * UrgencyGWei) ** 2) + ((ElapsedTime/ElapsedTimeBase) ** 2) * ElapsedTimeImportance + TargetPriceGWei", ElapsedTimeBase: 10 * time.Minute, ElapsedTimeImportance: 10, + DisableNewTx: false, } var DefaultDataPosterConfigForValidator = func() DataPosterConfig { @@ -1276,10 +1395,11 @@ var TestDataPosterConfig = DataPosterConfig{ UseDBStorage: false, UseNoOpStorage: false, LegacyStorageEncoding: false, - ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction"}, + ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction", InsecureSkipVerify: true}, MaxFeeCapFormula: "((BacklogOfBatches * UrgencyGWei) ** 2) + ((ElapsedTime/ElapsedTimeBase) ** 2) * ElapsedTimeImportance + TargetPriceGWei", ElapsedTimeBase: 10 * time.Minute, ElapsedTimeImportance: 10, + DisableNewTx: false, } var TestDataPosterConfigForValidator = func() DataPosterConfig { diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index a8e2e110a..f840d8c84 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "net/http" "testing" "time" @@ -14,11 +13,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/google/go-cmp/cmp" "github.com/holiman/uint256" - "github.com/offchainlabs/nitro/arbnode/dataposter/externalsigner" "github.com/offchainlabs/nitro/arbnode/dataposter/externalsignertest" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -58,14 +57,14 @@ func TestParseReplacementTimes(t *testing.T) { } } -func signerTestCfg(addr common.Address) (*ExternalSignerCfg, error) { +func signerTestCfg(addr common.Address, url string) (*ExternalSignerCfg, error) { cp, err := externalsignertest.CertPaths() if err != nil { return nil, fmt.Errorf("getting certificates path: %w", err) } return &ExternalSignerCfg{ Address: common.Bytes2Hex(addr.Bytes()), - URL: externalsignertest.SignerURL, + URL: url, Method: externalsignertest.SignerMethod, RootCA: cp.ServerCert, ClientCert: cp.ClientCert, @@ -106,15 +105,14 @@ var ( ) func TestExternalSigner(t *testing.T) { - httpSrv, srv := externalsignertest.NewServer(t) - cert, key := "./testdata/localhost.crt", "./testdata/localhost.key" + srv := externalsignertest.NewServer(t) go func() { - if err := httpSrv.ListenAndServeTLS(cert, key); err != nil && err != http.ErrServerClosed { - t.Errorf("ListenAndServeTLS() unexpected error: %v", err) + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) return } }() - signerCfg, err := signerTestCfg(srv.Address) + signerCfg, err := signerTestCfg(srv.Address, srv.URL()) if err != nil { t.Fatalf("Error getting signer test config: %v", err) } @@ -143,11 +141,7 @@ func TestExternalSigner(t *testing.T) { if err != nil { t.Fatalf("Error signing transaction with external signer: %v", err) } - args, err := externalsigner.TxToSignTxArgs(addr, tc.tx) - if err != nil { - t.Fatalf("Error converting transaction to sendTxArgs: %v", err) - } - want, err := srv.SignerFn(addr, args.ToTransaction()) + want, err := srv.SignerFn(addr, tc.tx) if err != nil { t.Fatalf("Error signing transaction: %v", err) } diff --git a/arbnode/dataposter/externalsigner/externalsigner.go b/arbnode/dataposter/externalsigner/externalsigner.go deleted file mode 100644 index 10d9754cd..000000000 --- a/arbnode/dataposter/externalsigner/externalsigner.go +++ /dev/null @@ -1,115 +0,0 @@ -package externalsigner - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/ethereum/go-ethereum/signer/core/apitypes" - "github.com/holiman/uint256" -) - -type SignTxArgs struct { - *apitypes.SendTxArgs - - // Feilds for BlobTx type transactions. - BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"` - BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"` - - // Blob sidecar fields for BlobTx type transactions. - // These are optional if BlobHashes are already present, since these - // are not included in the hash/signature. - Blobs []kzg4844.Blob `json:"blobs"` - Commitments []kzg4844.Commitment `json:"commitments"` - Proofs []kzg4844.Proof `json:"proofs"` -} - -func (a *SignTxArgs) ToTransaction() *types.Transaction { - if !a.isEIP4844() { - return a.SendTxArgs.ToTransaction() - } - to := common.Address{} - if a.To != nil { - to = a.To.Address() - } - var input []byte - if a.Input != nil { - input = *a.Input - } else if a.Data != nil { - input = *a.Data - } - al := types.AccessList{} - if a.AccessList != nil { - al = *a.AccessList - } - return types.NewTx(&types.BlobTx{ - To: to, - Nonce: uint64(a.SendTxArgs.Nonce), - Gas: uint64(a.Gas), - GasFeeCap: uint256.NewInt(a.MaxFeePerGas.ToInt().Uint64()), - GasTipCap: uint256.NewInt(a.MaxPriorityFeePerGas.ToInt().Uint64()), - Value: uint256.NewInt(a.Value.ToInt().Uint64()), - Data: input, - AccessList: al, - BlobFeeCap: uint256.NewInt(a.BlobFeeCap.ToInt().Uint64()), - BlobHashes: a.BlobHashes, - Sidecar: &types.BlobTxSidecar{ - Blobs: a.Blobs, - Commitments: a.Commitments, - Proofs: a.Proofs, - }, - ChainID: uint256.NewInt(a.ChainID.ToInt().Uint64()), - }) -} - -func (a *SignTxArgs) isEIP4844() bool { - return a.BlobHashes != nil || a.BlobFeeCap != nil -} - -// TxToSignTxArgs converts transaction to SendTxArgs. This is needed for -// external signer to specify From field. -func TxToSignTxArgs(addr common.Address, tx *types.Transaction) (*SignTxArgs, error) { - var to *common.MixedcaseAddress - if tx.To() != nil { - to = new(common.MixedcaseAddress) - *to = common.NewMixedcaseAddress(*tx.To()) - } - data := (hexutil.Bytes)(tx.Data()) - val := (*hexutil.Big)(tx.Value()) - if val == nil { - val = (*hexutil.Big)(big.NewInt(0)) - } - al := tx.AccessList() - var ( - blobs []kzg4844.Blob - commitments []kzg4844.Commitment - proofs []kzg4844.Proof - ) - if tx.BlobTxSidecar() != nil { - blobs = tx.BlobTxSidecar().Blobs - commitments = tx.BlobTxSidecar().Commitments - proofs = tx.BlobTxSidecar().Proofs - } - return &SignTxArgs{ - SendTxArgs: &apitypes.SendTxArgs{ - From: common.NewMixedcaseAddress(addr), - To: to, - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - MaxFeePerGas: (*hexutil.Big)(tx.GasFeeCap()), - MaxPriorityFeePerGas: (*hexutil.Big)(tx.GasTipCap()), - Value: *val, - Nonce: hexutil.Uint64(tx.Nonce()), - Data: &data, - AccessList: &al, - ChainID: (*hexutil.Big)(tx.ChainId()), - }, - BlobFeeCap: (*hexutil.Big)(tx.BlobGasFeeCap()), - BlobHashes: tx.BlobHashes(), - Blobs: blobs, - Commitments: commitments, - Proofs: proofs, - }, nil -} diff --git a/arbnode/dataposter/externalsigner/externalsigner_test.go b/arbnode/dataposter/externalsigner/externalsigner_test.go deleted file mode 100644 index abd5acedc..000000000 --- a/arbnode/dataposter/externalsigner/externalsigner_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package externalsigner - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/holiman/uint256" -) - -var ( - blobTx = types.NewTx( - &types.BlobTx{ - ChainID: uint256.NewInt(1337), - Nonce: 13, - GasTipCap: uint256.NewInt(1), - GasFeeCap: uint256.NewInt(1), - Gas: 3, - To: common.Address{}, - Value: uint256.NewInt(1), - Data: []byte{0x01, 0x02, 0x03}, - BlobHashes: []common.Hash{ - common.BigToHash(big.NewInt(1)), - common.BigToHash(big.NewInt(2)), - common.BigToHash(big.NewInt(3)), - }, - Sidecar: &types.BlobTxSidecar{}, - }, - ) - dynamicFeeTx = types.NewTx( - &types.DynamicFeeTx{ - ChainID: big.NewInt(1337), - Nonce: 13, - GasTipCap: big.NewInt(1), - GasFeeCap: big.NewInt(1), - Gas: 3, - To: nil, - Value: big.NewInt(1), - Data: []byte{0x01, 0x02, 0x03}, - }, - ) -) - -// TestToTranssaction tests that tranasction converted to SignTxArgs and then -// back to Transaction results in the same hash. -func TestToTranssaction(t *testing.T) { - for _, tc := range []struct { - desc string - tx *types.Transaction - }{ - { - desc: "blob transaction", - tx: blobTx, - }, - { - desc: "dynamic fee transaction", - tx: dynamicFeeTx, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - signTxArgs, err := TxToSignTxArgs(common.Address{}, tc.tx) - if err != nil { - t.Fatalf("TxToSignTxArgs() unexpected error: %v", err) - } - got := signTxArgs.ToTransaction() - hasher := types.LatestSignerForChainID(nil) - if h, g := hasher.Hash(tc.tx), hasher.Hash(got); h != g { - t.Errorf("ToTransaction() got hash: %v want: %v", g, h) - } - }) - } - -} diff --git a/arbnode/dataposter/externalsignertest/externalsignertest.go b/arbnode/dataposter/externalsignertest/externalsignertest.go index 73a5760fb..554defc76 100644 --- a/arbnode/dataposter/externalsignertest/externalsignertest.go +++ b/arbnode/dataposter/externalsignertest/externalsignertest.go @@ -4,8 +4,10 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "fmt" "math/big" + "net" "net/http" "os" "path/filepath" @@ -19,16 +21,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/nitro/arbnode/dataposter/externalsigner" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/offchainlabs/nitro/util/testhelpers" ) var ( dataPosterPath = "arbnode/dataposter" selfPath = filepath.Join(dataPosterPath, "externalsignertest") - - SignerPort = 1234 - SignerURL = fmt.Sprintf("https://localhost:%v", SignerPort) - SignerMethod = "test_signTransaction" + SignerMethod = "test_signTransaction" ) type CertAbsPaths struct { @@ -38,6 +38,12 @@ type CertAbsPaths struct { ClientKey string } +type SignerServer struct { + *http.Server + *SignerAPI + listener net.Listener +} + func basePath() (string, error) { _, file, _, ok := runtime.Caller(1) if !ok { @@ -71,7 +77,7 @@ func CertPaths() (*CertAbsPaths, error) { }, nil } -func NewServer(t *testing.T) (*http.Server, *SignerAPI) { +func NewServer(t *testing.T) *SignerServer { rpcServer := rpc.NewServer() signer, address, err := setupAccount("/tmp/keystore") if err != nil { @@ -94,8 +100,13 @@ func NewServer(t *testing.T) (*http.Server, *SignerAPI) { pool := x509.NewCertPool() pool.AppendCertsFromPEM(clientCert) + ln, err := testhelpers.FreeTCPPortListener() + if err != nil { + t.Fatalf("Error getting a listener on a free TCP port: %v", err) + } + httpServer := &http.Server{ - Addr: fmt.Sprintf(":%d", SignerPort), + Addr: ln.Addr().String(), Handler: rpcServer, ReadTimeout: 30 * time.Second, ReadHeaderTimeout: 30 * time.Second, @@ -109,12 +120,36 @@ func NewServer(t *testing.T) (*http.Server, *SignerAPI) { } t.Cleanup(func() { - if err := httpServer.Close(); err != nil { + if err := httpServer.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) { t.Fatalf("Error shutting down http server: %v", err) } + // Explicitly close the listner in case the server was never started. + if err := ln.Close(); err != nil && !errors.Is(err, net.ErrClosed) { + t.Fatalf("Error closing listener: %v", err) + } }) - return httpServer, s + return &SignerServer{httpServer, s, ln} +} + +// URL returns the URL of the signer server. +// +// Note: The server must return "localhost" for the hostname part of +// the URL to match the expectations from the TLS certificate. +func (s *SignerServer) URL() string { + port := strings.Split(s.Addr, ":")[1] + return fmt.Sprintf("https://localhost:%s", port) +} + +func (s *SignerServer) Start() error { + cp, err := CertPaths() + if err != nil { + return err + } + if err := s.ServeTLS(s.listener, cp.ServerCert, cp.ServerKey); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil } // setupAccount creates a new account in a given directory, unlocks it, creates @@ -144,11 +179,15 @@ type SignerAPI struct { Address common.Address } -func (a *SignerAPI) SignTransaction(ctx context.Context, req *externalsigner.SignTxArgs) (hexutil.Bytes, error) { +func (a *SignerAPI) SignTransaction(ctx context.Context, req *apitypes.SendTxArgs) (hexutil.Bytes, error) { if req == nil { return nil, fmt.Errorf("nil request") } - signedTx, err := a.SignerFn(a.Address, req.ToTransaction()) + tx, err := req.ToTransaction() + if err != nil { + return nil, fmt.Errorf("converting send transaction arguments to transaction: %w", err) + } + signedTx, err := a.SignerFn(a.Address, tx) if err != nil { return nil, fmt.Errorf("signing transaction: %w", err) } diff --git a/arbnode/dataposter/storage_test.go b/arbnode/dataposter/storage_test.go index f98c120f3..e2aa321e0 100644 --- a/arbnode/dataposter/storage_test.go +++ b/arbnode/dataposter/storage_test.go @@ -19,6 +19,7 @@ import ( "github.com/offchainlabs/nitro/arbnode/dataposter/redis" "github.com/offchainlabs/nitro/arbnode/dataposter/slice" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" + "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" @@ -44,7 +45,7 @@ func newLevelDBStorage(t *testing.T, encF storage.EncoderDecoderF) *dbstorage.St func newPebbleDBStorage(t *testing.T, encF storage.EncoderDecoderF) *dbstorage.Storage { t.Helper() - db, err := rawdb.NewPebbleDBDatabase(path.Join(t.TempDir(), "pebble.db"), 0, 0, "default", false, true) + db, err := rawdb.NewPebbleDBDatabase(path.Join(t.TempDir(), "pebble.db"), 0, 0, "default", false, true, conf.PersistentConfigDefault.Pebble.ExtraOptions("pebble")) if err != nil { t.Fatalf("NewPebbleDBDatabase() unexpected error: %v", err) } diff --git a/arbnode/inbox_reader.go b/arbnode/inbox_reader.go index 72881b52f..3ba9aa78f 100644 --- a/arbnode/inbox_reader.go +++ b/arbnode/inbox_reader.go @@ -10,7 +10,6 @@ import ( "math" "math/big" "strings" - "sync" "sync/atomic" "time" @@ -99,10 +98,6 @@ type InboxReader struct { // Atomic lastSeenBatchCount uint64 - - // Behind the mutex - lastReadMutex sync.RWMutex - lastReadBlock uint64 lastReadBatchCount uint64 } @@ -144,6 +139,9 @@ func (r *InboxReader) Start(ctxIn context.Context) error { return err } if batchCount > 0 { + if r.tracker.snapSyncConfig.Enabled { + break + } // Validate the init message matches our L2 blockchain message, err := r.tracker.GetDelayedMessage(0) if err != nil { @@ -396,10 +394,7 @@ func (r *InboxReader) run(ctx context.Context, hadError bool) error { // There's nothing to do from = arbmath.BigAddByUint(currentHeight, 1) blocksToFetch = config.DefaultBlocksToRead - r.lastReadMutex.Lock() - r.lastReadBlock = currentHeight.Uint64() - r.lastReadBatchCount = checkingBatchCount - r.lastReadMutex.Unlock() + atomic.StoreUint64(&r.lastReadBatchCount, checkingBatchCount) storeSeenBatchCount() if !r.caughtUp && readMode == "latest" { r.caughtUp = true @@ -531,10 +526,7 @@ func (r *InboxReader) run(ctx context.Context, hadError bool) error { } if len(sequencerBatches) > 0 { readAnyBatches = true - r.lastReadMutex.Lock() - r.lastReadBlock = to.Uint64() - r.lastReadBatchCount = sequencerBatches[len(sequencerBatches)-1].SequenceNumber + 1 - r.lastReadMutex.Unlock() + atomic.StoreUint64(&r.lastReadBatchCount, sequencerBatches[len(sequencerBatches)-1].SequenceNumber+1) storeSeenBatchCount() } } @@ -561,10 +553,7 @@ func (r *InboxReader) run(ctx context.Context, hadError bool) error { } if !readAnyBatches { - r.lastReadMutex.Lock() - r.lastReadBlock = currentHeight.Uint64() - r.lastReadBatchCount = checkingBatchCount - r.lastReadMutex.Unlock() + atomic.StoreUint64(&r.lastReadBatchCount, checkingBatchCount) storeSeenBatchCount() } } @@ -635,10 +624,8 @@ func (r *InboxReader) GetSequencerMessageBytes(ctx context.Context, seqNum uint6 return nil, common.Hash{}, fmt.Errorf("sequencer batch %v not found in L1 block %v (found batches %v)", seqNum, metadata.ParentChainBlock, seenBatches) } -func (r *InboxReader) GetLastReadBlockAndBatchCount() (uint64, uint64) { - r.lastReadMutex.RLock() - defer r.lastReadMutex.RUnlock() - return r.lastReadBlock, r.lastReadBatchCount +func (r *InboxReader) GetLastReadBatchCount() uint64 { + return atomic.LoadUint64(&r.lastReadBatchCount) } // GetLastSeenBatchCount returns how many sequencer batches the inbox reader has read in from L1. diff --git a/arbnode/inbox_test.go b/arbnode/inbox_test.go index e979979de..252d7c9b7 100644 --- a/arbnode/inbox_test.go +++ b/arbnode/inbox_test.go @@ -65,8 +65,9 @@ func NewTransactionStreamerForTest(t *testing.T, ownerAddress common.Address) (* if err != nil { Fail(t, err) } + execEngine.Initialize(gethexec.DefaultCachingConfig.StylusLRUCache) execSeq := &execClientWrapper{execEngine, t} - inbox, err := NewTransactionStreamer(arbDb, bc.Config(), execSeq, nil, make(chan error, 1), transactionStreamerConfigFetcher) + inbox, err := NewTransactionStreamer(arbDb, bc.Config(), execSeq, nil, make(chan error, 1), transactionStreamerConfigFetcher, &DefaultSnapSyncConfig) if err != nil { Fail(t, err) } @@ -233,7 +234,7 @@ func TestTransactionStreamer(t *testing.T) { Fail(t, "error getting block state", err) } haveBalance := state.GetBalance(acct) - if balance.Cmp(haveBalance) != 0 { + if balance.Cmp(haveBalance.ToBig()) != 0 { t.Error("unexpected balance for account", acct, "; expected", balance, "got", haveBalance) } } diff --git a/arbnode/inbox_tracker.go b/arbnode/inbox_tracker.go index 1d2027941..aeb6d7d7a 100644 --- a/arbnode/inbox_tracker.go +++ b/arbnode/inbox_tracker.go @@ -20,10 +20,10 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/broadcaster" m "github.com/offchainlabs/nitro/broadcaster/message" - "github.com/offchainlabs/nitro/eigenda" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/util/containers" ) @@ -34,30 +34,24 @@ var ( ) type InboxTracker struct { - db ethdb.Database - txStreamer *TransactionStreamer - mutex sync.Mutex - validator *staker.BlockValidator - das arbstate.DataAvailabilityReader - blobReader arbstate.BlobReader - eigenDA eigenda.EigenDAReader + db ethdb.Database + txStreamer *TransactionStreamer + mutex sync.Mutex + validator *staker.BlockValidator + dapReaders []daprovider.Reader + snapSyncConfig SnapSyncConfig batchMetaMutex sync.Mutex batchMeta *containers.LruCache[uint64, BatchMetadata] } -func NewInboxTracker(db ethdb.Database, txStreamer *TransactionStreamer, das arbstate.DataAvailabilityReader, blobReader arbstate.BlobReader, eigenDAReader eigenda.EigenDAReader) (*InboxTracker, error) { - // We support a nil txStreamer for the pruning code - if txStreamer != nil && txStreamer.chainConfig.ArbitrumChainParams.DataAvailabilityCommittee && das == nil { - return nil, errors.New("data availability service required but unconfigured") - } +func NewInboxTracker(db ethdb.Database, txStreamer *TransactionStreamer, dapReaders []daprovider.Reader, snapSyncConfig SnapSyncConfig) (*InboxTracker, error) { tracker := &InboxTracker{ - db: db, - txStreamer: txStreamer, - das: das, - blobReader: blobReader, - eigenDA: eigenDAReader, - batchMeta: containers.NewLruCache[uint64, BatchMetadata](1000), + db: db, + txStreamer: txStreamer, + dapReaders: dapReaders, + batchMeta: containers.NewLruCache[uint64, BatchMetadata](1000), + snapSyncConfig: snapSyncConfig, } return tracker, nil } @@ -207,6 +201,11 @@ func (t *InboxTracker) GetBatchMessageCount(seqNum uint64) (arbutil.MessageIndex return metadata.MessageCount, err } +func (t *InboxTracker) GetBatchParentChainBlock(seqNum uint64) (uint64, error) { + metadata, err := t.GetBatchMetadata(seqNum) + return metadata.ParentChainBlock, err +} + // GetBatchAcc is a convenience function wrapping GetBatchMetadata func (t *InboxTracker) GetBatchAcc(seqNum uint64) (common.Hash, error) { metadata, err := t.GetBatchMetadata(seqNum) @@ -226,6 +225,54 @@ func (t *InboxTracker) GetBatchCount() (uint64, error) { return count, nil } +// err will return unexpected/internal errors +// bool will be false if batch not found (meaning, block not yet posted on a batch) +func (t *InboxTracker) FindInboxBatchContainingMessage(pos arbutil.MessageIndex) (uint64, bool, error) { + batchCount, err := t.GetBatchCount() + if err != nil { + return 0, false, err + } + low := uint64(0) + high := batchCount - 1 + lastBatchMessageCount, err := t.GetBatchMessageCount(high) + if err != nil { + return 0, false, err + } + if lastBatchMessageCount <= pos { + return 0, false, nil + } + // Iteration preconditions: + // - high >= low + // - msgCount(low - 1) <= pos implies low <= target + // - msgCount(high) > pos implies high >= target + // Therefore, if low == high, then low == high == target + for { + // Due to integer rounding, mid >= low && mid < high + mid := (low + high) / 2 + count, err := t.GetBatchMessageCount(mid) + if err != nil { + return 0, false, err + } + if count < pos { + // Must narrow as mid >= low, therefore mid + 1 > low, therefore newLow > oldLow + // Keeps low precondition as msgCount(mid) < pos + low = mid + 1 + } else if count == pos { + return mid + 1, true, nil + } else if count == pos+1 || mid == low { // implied: count > pos + return mid, true, nil + } else { + // implied: count > pos + 1 + // Must narrow as mid < high, therefore newHigh < oldHigh + // Keeps high precondition as msgCount(mid) > pos + high = mid + } + if high == low { + return high, true, nil + } + } +} + func (t *InboxTracker) PopulateFeedBacklog(broadcastServer *broadcaster.Broadcaster) error { batchCount, err := t.GetBatchCount() if err != nil { @@ -252,7 +299,14 @@ func (t *InboxTracker) PopulateFeedBacklog(broadcastServer *broadcaster.Broadcas if err != nil { return fmt.Errorf("error getting message %v: %w", seqNum, err) } - feedMessage, err := broadcastServer.NewBroadcastFeedMessage(*message, seqNum) + + msgResult, err := t.txStreamer.ResultAtCount(seqNum) + var blockHash *common.Hash + if err == nil { + blockHash = &msgResult.BlockHash + } + + feedMessage, err := broadcastServer.NewBroadcastFeedMessage(*message, seqNum, blockHash) if err != nil { return fmt.Errorf("error creating broadcast feed message %v: %w", seqNum, err) } @@ -333,16 +387,40 @@ func (t *InboxTracker) GetDelayedMessageBytes(seqNum uint64) ([]byte, error) { } func (t *InboxTracker) AddDelayedMessages(messages []*DelayedInboxMessage, hardReorg bool) error { + var nextAcc common.Hash + firstDelayedMsgToKeep := uint64(0) if len(messages) == 0 { return nil } - t.mutex.Lock() - defer t.mutex.Unlock() - pos, err := messages[0].Message.Header.SeqNum() if err != nil { return err } + if t.snapSyncConfig.Enabled && pos < t.snapSyncConfig.DelayedCount { + firstDelayedMsgToKeep = t.snapSyncConfig.DelayedCount + if firstDelayedMsgToKeep > 0 { + firstDelayedMsgToKeep-- + } + for { + if len(messages) == 0 { + return nil + } + pos, err = messages[0].Message.Header.SeqNum() + if err != nil { + return err + } + if pos+1 == firstDelayedMsgToKeep { + nextAcc = messages[0].AfterInboxAcc() + } + if pos < firstDelayedMsgToKeep { + messages = messages[1:] + } else { + break + } + } + } + t.mutex.Lock() + defer t.mutex.Unlock() if !hardReorg { // This math is safe to do as we know len(messages) > 0 @@ -357,8 +435,7 @@ func (t *InboxTracker) AddDelayedMessages(messages []*DelayedInboxMessage, hardR } } - var nextAcc common.Hash - if pos > 0 { + if pos > firstDelayedMsgToKeep { var err error nextAcc, err = t.GetDelayedAcc(pos - 1) if err != nil { @@ -546,17 +623,44 @@ func (b *multiplexerBackend) ReadDelayedInbox(seqNum uint64) (*arbostypes.L1Inco var delayedMessagesMismatch = errors.New("sequencer batch delayed messages missing or different") func (t *InboxTracker) AddSequencerBatches(ctx context.Context, client arbutil.L1Interface, batches []*SequencerInboxBatch) error { + var nextAcc common.Hash + var prevbatchmeta BatchMetadata + sequenceNumberToKeep := uint64(0) if len(batches) == 0 { return nil } + if t.snapSyncConfig.Enabled && batches[0].SequenceNumber < t.snapSyncConfig.BatchCount { + sequenceNumberToKeep = t.snapSyncConfig.BatchCount + if sequenceNumberToKeep > 0 { + sequenceNumberToKeep-- + } + for { + if len(batches) == 0 { + return nil + } + if batches[0].SequenceNumber+1 == sequenceNumberToKeep { + nextAcc = batches[0].AfterInboxAcc + prevbatchmeta = BatchMetadata{ + Accumulator: batches[0].AfterInboxAcc, + DelayedMessageCount: batches[0].AfterDelayedCount, + MessageCount: arbutil.MessageIndex(t.snapSyncConfig.PrevBatchMessageCount), + ParentChainBlock: batches[0].ParentChainBlockNumber, + } + } + if batches[0].SequenceNumber < sequenceNumberToKeep { + batches = batches[1:] + } else { + break + } + } + } t.mutex.Lock() defer t.mutex.Unlock() pos := batches[0].SequenceNumber startPos := pos - var nextAcc common.Hash - var prevbatchmeta BatchMetadata - if pos > 0 { + + if pos > sequenceNumberToKeep { var err error prevbatchmeta, err = t.GetBatchMetadata(pos - 1) nextAcc = prevbatchmeta.Accumulator @@ -609,17 +713,7 @@ func (t *InboxTracker) AddSequencerBatches(ctx context.Context, client arbutil.L ctx: ctx, client: client, } - var daProviders []arbstate.DataAvailabilityProvider - if t.das != nil { - daProviders = append(daProviders, arbstate.NewDAProviderDAS(t.das)) - } - if t.blobReader != nil { - daProviders = append(daProviders, arbstate.NewDAProviderBlobReader(t.blobReader)) - } - if t.eigenDA != nil { - daProviders = append(daProviders, arbstate.NewDAProviderEigenDA(t.eigenDA)) - } - multiplexer := arbstate.NewInboxMultiplexer(backend, prevbatchmeta.DelayedMessageCount, daProviders, arbstate.KeysetValidate) + multiplexer := arbstate.NewInboxMultiplexer(backend, prevbatchmeta.DelayedMessageCount, t.dapReaders, daprovider.KeysetValidate) batchMessageCounts := make(map[uint64]arbutil.MessageIndex) currentpos := prevbatchmeta.MessageCount + 1 diff --git a/arbnode/message_pruner.go b/arbnode/message_pruner.go index 31bf1a63f..5d18341a2 100644 --- a/arbnode/message_pruner.go +++ b/arbnode/message_pruner.go @@ -23,13 +23,14 @@ import ( type MessagePruner struct { stopwaiter.StopWaiter - transactionStreamer *TransactionStreamer - inboxTracker *InboxTracker - config MessagePrunerConfigFetcher - pruningLock sync.Mutex - lastPruneDone time.Time - cachedPrunedMessages uint64 - cachedPrunedDelayedMessages uint64 + transactionStreamer *TransactionStreamer + inboxTracker *InboxTracker + config MessagePrunerConfigFetcher + pruningLock sync.Mutex + lastPruneDone time.Time + cachedPrunedMessages uint64 + cachedPrunedBlockHashesInputFeed uint64 + cachedPrunedDelayedMessages uint64 } type MessagePrunerConfig struct { @@ -115,7 +116,15 @@ func (m *MessagePruner) prune(ctx context.Context, count arbutil.MessageIndex, g } func (m *MessagePruner) deleteOldMessagesFromDB(ctx context.Context, messageCount arbutil.MessageIndex, delayedMessageCount uint64) error { - prunedKeysRange, err := deleteFromLastPrunedUptoEndKey(ctx, m.transactionStreamer.db, messagePrefix, &m.cachedPrunedMessages, uint64(messageCount)) + prunedKeysRange, err := deleteFromLastPrunedUptoEndKey(ctx, m.transactionStreamer.db, blockHashInputFeedPrefix, &m.cachedPrunedBlockHashesInputFeed, uint64(messageCount)) + if err != nil { + return fmt.Errorf("error deleting expected block hashes: %w", err) + } + if len(prunedKeysRange) > 0 { + log.Info("Pruned expected block hashes:", "first pruned key", prunedKeysRange[0], "last pruned key", prunedKeysRange[len(prunedKeysRange)-1]) + } + + prunedKeysRange, err = deleteFromLastPrunedUptoEndKey(ctx, m.transactionStreamer.db, messagePrefix, &m.cachedPrunedMessages, uint64(messageCount)) if err != nil { return fmt.Errorf("error deleting last batch messages: %w", err) } diff --git a/arbnode/message_pruner_test.go b/arbnode/message_pruner_test.go index 0212ed236..ed85c0ebc 100644 --- a/arbnode/message_pruner_test.go +++ b/arbnode/message_pruner_test.go @@ -22,8 +22,8 @@ func TestMessagePrunerWithPruningEligibleMessagePresent(t *testing.T) { Require(t, err) checkDbKeys(t, messagesCount, transactionStreamerDb, messagePrefix) + checkDbKeys(t, messagesCount, transactionStreamerDb, blockHashInputFeedPrefix) checkDbKeys(t, messagesCount, inboxTrackerDb, rlpDelayedMessagePrefix) - } func TestMessagePrunerTwoHalves(t *testing.T) { @@ -71,16 +71,18 @@ func TestMessagePrunerWithNoPruningEligibleMessagePresent(t *testing.T) { Require(t, err) checkDbKeys(t, uint64(messagesCount), transactionStreamerDb, messagePrefix) + checkDbKeys(t, uint64(messagesCount), transactionStreamerDb, blockHashInputFeedPrefix) checkDbKeys(t, messagesCount, inboxTrackerDb, rlpDelayedMessagePrefix) } func setupDatabase(t *testing.T, messageCount, delayedMessageCount uint64) (ethdb.Database, ethdb.Database, *MessagePruner) { - transactionStreamerDb := rawdb.NewMemoryDatabase() for i := uint64(0); i < uint64(messageCount); i++ { err := transactionStreamerDb.Put(dbKey(messagePrefix, i), []byte{}) Require(t, err) + err = transactionStreamerDb.Put(dbKey(blockHashInputFeedPrefix, i), []byte{}) + Require(t, err) } inboxTrackerDb := rawdb.NewMemoryDatabase() diff --git a/arbnode/node.go b/arbnode/node.go index 0cd3e99a1..c2e3517c8 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -26,7 +26,8 @@ import ( "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbnode/resourcemanager" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/broadcastclient" "github.com/offchainlabs/nitro/broadcastclients" @@ -94,6 +95,8 @@ type Config struct { TransactionStreamer TransactionStreamerConfig `koanf:"transaction-streamer" reload:"hot"` Maintenance MaintenanceConfig `koanf:"maintenance" reload:"hot"` ResourceMgmt resourcemanager.Config `koanf:"resource-mgmt" reload:"hot"` + // SnapSyncConfig is only used for testing purposes, these should not be configured in production. + SnapSyncTest SnapSyncConfig } func (c *Config) Validate() error { @@ -176,6 +179,7 @@ var ConfigDefault = Config{ TransactionStreamer: DefaultTransactionStreamerConfig, ResourceMgmt: resourcemanager.DefaultConfig, Maintenance: DefaultMaintenanceConfig, + SnapSyncTest: DefaultSnapSyncConfig, } func ConfigDefaultL1Test() *Config { @@ -198,6 +202,7 @@ func ConfigDefaultL1NonSequencerTest() *Config { config.BatchPoster.Enable = false config.SeqCoordinator.Enable = false config.BlockValidator = staker.TestBlockValidatorConfig + config.SyncMonitor = TestSyncMonitorConfig config.Staker = staker.TestL1ValidatorConfig config.Staker.Enable = false config.BlockValidator.ValidationServerConfigs = []rpcclient.ClientConfig{{URL: ""}} @@ -215,6 +220,7 @@ func ConfigDefaultL2Test() *Config { config.SeqCoordinator.Signer.ECDSA.AcceptSequencer = false config.SeqCoordinator.Signer.ECDSA.Dangerous.AcceptMissing = true config.Staker = staker.TestL1ValidatorConfig + config.SyncMonitor = TestSyncMonitorConfig config.Staker.Enable = false config.BlockValidator.ValidationServerConfigs = []rpcclient.ClientConfig{{URL: ""}} config.TransactionStreamer = DefaultTransactionStreamerConfig @@ -253,7 +259,7 @@ type Node struct { L1Reader *headerreader.HeaderReader TxStreamer *TransactionStreamer DeployInfo *chaininfo.RollupAddresses - BlobReader arbstate.BlobReader + BlobReader daprovider.BlobReader InboxReader *InboxReader InboxTracker *InboxTracker DelayedSequencer *DelayedSequencer @@ -267,12 +273,27 @@ type Node struct { SeqCoordinator *SeqCoordinator MaintenanceRunner *MaintenanceRunner DASLifecycleManager *das.LifecycleManager - ClassicOutboxRetriever *ClassicOutboxRetriever SyncMonitor *SyncMonitor configFetcher ConfigFetcher ctx context.Context } +type SnapSyncConfig struct { + Enabled bool + PrevBatchMessageCount uint64 + PrevDelayedRead uint64 + BatchCount uint64 + DelayedCount uint64 +} + +var DefaultSnapSyncConfig = SnapSyncConfig{ + Enabled: false, + PrevBatchMessageCount: 0, + BatchCount: 0, + DelayedCount: 0, + PrevDelayedRead: 0, +} + type ConfigFetcher interface { Get() *Config Start(context.Context) @@ -372,7 +393,7 @@ func createNodeImpl( dataSigner signature.DataSignerFunc, fatalErrChan chan error, parentChainID *big.Int, - blobReader arbstate.BlobReader, + blobReader daprovider.BlobReader, ) (*Node, error) { config := configFetcher.Get() @@ -383,17 +404,10 @@ func createNodeImpl( l2ChainId := l2Config.ChainID.Uint64() - syncMonitor := NewSyncMonitor(&config.SyncMonitor) - var classicOutbox *ClassicOutboxRetriever - classicMsgDb, err := stack.OpenDatabase("classic-msg", 0, 0, "", true) - if err != nil { - if l2Config.ArbitrumChainParams.GenesisBlockNum > 0 { - log.Warn("Classic Msg Database not found", "err", err) - } - classicOutbox = nil - } else { - classicOutbox = NewClassicOutboxRetriever(classicMsgDb) + syncConfigFetcher := func() *SyncMonitorConfig { + return &configFetcher.Get().SyncMonitor } + syncMonitor := NewSyncMonitor(syncConfigFetcher) var l1Reader *headerreader.HeaderReader if config.ParentChainReader.Enable { @@ -417,7 +431,7 @@ func createNodeImpl( } transactionStreamerConfigFetcher := func() *TransactionStreamerConfig { return &configFetcher.Get().TransactionStreamer } - txStreamer, err := NewTransactionStreamer(arbDb, l2Config, exec, broadcastServer, fatalErrChan, transactionStreamerConfigFetcher) + txStreamer, err := NewTransactionStreamer(arbDb, l2Config, exec, broadcastServer, fatalErrChan, transactionStreamerConfigFetcher, &configFetcher.Get().SnapSyncTest) if err != nil { return nil, err } @@ -490,7 +504,6 @@ func createNodeImpl( SeqCoordinator: coordinator, MaintenanceRunner: maintenanceRunner, DASLifecycleManager: nil, - ClassicOutboxRetriever: classicOutbox, SyncMonitor: syncMonitor, configFetcher: configFetcher, ctx: ctx, @@ -546,9 +559,18 @@ func createNodeImpl( eigenDAWriter = eigenDAService } - log.Info("EigenDA reader", "reader", eigenDAReader) - - inboxTracker, err := NewInboxTracker(arbDb, txStreamer, daReader, blobReader, eigenDAReader) + // We support a nil txStreamer for the pruning code + if txStreamer != nil && txStreamer.chainConfig.ArbitrumChainParams.DataAvailabilityCommittee && daReader == nil { + return nil, errors.New("data availability service required but unconfigured") + } + var dapReaders []daprovider.Reader + if daReader != nil { + dapReaders = append(dapReaders, daprovider.NewReaderForDAS(daReader)) + } + if blobReader != nil { + dapReaders = append(dapReaders, daprovider.NewReaderForBlobReader(blobReader)) + } + inboxTracker, err := NewInboxTracker(arbDb, txStreamer, dapReaders, config.SnapSyncTest) if err != nil { return nil, err } @@ -559,16 +581,14 @@ func createNodeImpl( txStreamer.SetInboxReaders(inboxReader, delayedBridge) var statelessBlockValidator *staker.StatelessBlockValidator - if config.BlockValidator.ValidationServerConfigs[0].URL != "" { + if config.BlockValidator.RedisValidationClientConfig.Enabled() || config.BlockValidator.ValidationServerConfigs[0].URL != "" { statelessBlockValidator, err = staker.NewStatelessBlockValidator( inboxReader, inboxTracker, txStreamer, exec, rawdb.NewTable(arbDb, storage.BlockValidatorPrefix), - daReader, - blobReader, - eigenDAReader, + dapReaders, func() *staker.BlockValidatorConfig { return &configFetcher.Get().BlockValidator }, stack, ) @@ -599,6 +619,7 @@ func createNodeImpl( var stakerObj *staker.Staker var messagePruner *MessagePruner + var stakerAddr common.Address if config.Staker.Enable { dp, err := StakerDataposter( @@ -656,17 +677,14 @@ func createNodeImpl( if err := wallet.Initialize(ctx); err != nil { return nil, err } - var validatorAddr string - if txOptsValidator != nil { - validatorAddr = txOptsValidator.From.String() - } else { - validatorAddr = config.Staker.DataPoster.ExternalSigner.Address + if dp != nil { + stakerAddr = dp.Sender() } whitelisted, err := stakerObj.IsWhitelisted(ctx) if err != nil { return nil, err } - log.Info("running as validator", "txSender", validatorAddr, "actingAsWallet", wallet.Address(), "whitelisted", whitelisted, "strategy", config.Staker.Strategy) + log.Info("running as validator", "txSender", stakerAddr, "actingAsWallet", wallet.Address(), "whitelisted", whitelisted, "strategy", config.Staker.Strategy) } var batchPoster *BatchPoster @@ -675,6 +693,10 @@ func createNodeImpl( if txOptsBatchPoster == nil && config.BatchPoster.DataPoster.ExternalSigner.URL == "" { return nil, errors.New("batchposter, but no TxOpts") } + var dapWriter daprovider.Writer + if daWriter != nil { + dapWriter = daprovider.NewWriterForDAS(daWriter) + } batchPoster, err = NewBatchPoster(ctx, &BatchPosterOpts{ DataPosterDB: rawdb.NewTable(arbDb, storage.BatchPosterPrefix), L1Reader: l1Reader, @@ -685,13 +707,17 @@ func createNodeImpl( Config: func() *BatchPosterConfig { return &configFetcher.Get().BatchPoster }, DeployInfo: deployInfo, TransactOpts: txOptsBatchPoster, - DAWriter: daWriter, - EigenDAWriter: eigenDAWriter, + DAPWriter: dapWriter, ParentChainID: parentChainID, }) if err != nil { return nil, err } + + // Check if staker and batch poster are using the same address + if stakerAddr != (common.Address{}) && !strings.EqualFold(config.Staker.Strategy, "watchtower") && stakerAddr == batchPoster.dataPoster.Sender() { + return nil, fmt.Errorf("staker and batch poster are using the same address which is not allowed: %v", stakerAddr) + } } // always create DelayedSequencer, it won't do anything if it is disabled @@ -721,7 +747,6 @@ func createNodeImpl( SeqCoordinator: coordinator, MaintenanceRunner: maintenanceRunner, DASLifecycleManager: dasLifecycleManager, - ClassicOutboxRetriever: classicOutbox, SyncMonitor: syncMonitor, configFetcher: configFetcher, ctx: ctx, @@ -747,7 +772,7 @@ func CreateNode( dataSigner signature.DataSignerFunc, fatalErrChan chan error, parentChainID *big.Int, - blobReader arbstate.BlobReader, + blobReader daprovider.BlobReader, ) (*Node, error) { currentNode, err := createNodeImpl(ctx, stack, exec, arbDb, configFetcher, l2Config, l1client, deployInfo, txOptsValidator, txOptsBatchPoster, dataSigner, fatalErrChan, parentChainID, blobReader) if err != nil { @@ -784,16 +809,19 @@ func (n *Node) Start(ctx context.Context) error { execClient = nil } if execClient != nil { - err := execClient.Initialize(ctx, n, n.SyncMonitor) + err := execClient.Initialize(ctx) if err != nil { return fmt.Errorf("error initializing exec client: %w", err) } } - n.SyncMonitor.Initialize(n.InboxReader, n.TxStreamer, n.SeqCoordinator, n.Execution) + n.SyncMonitor.Initialize(n.InboxReader, n.TxStreamer, n.SeqCoordinator) err := n.Stack.Start() if err != nil { return fmt.Errorf("error starting geth stack: %w", err) } + if execClient != nil { + execClient.SetConsensusClient(n) + } err = n.Execution.Start(ctx) if err != nil { return fmt.Errorf("error starting exec client: %w", err) @@ -906,6 +934,7 @@ func (n *Node) Start(ctx context.Context) error { if n.configFetcher != nil { n.configFetcher.Start(ctx) } + n.SyncMonitor.Start(ctx) return nil } @@ -959,6 +988,7 @@ func (n *Node) StopAndWait() { // Just stops the redis client (most other stuff was stopped earlier) n.SeqCoordinator.StopAndWait() } + n.SyncMonitor.StopAndWait() if n.DASLifecycleManager != nil { n.DASLifecycleManager.StopAndWaitUntil(2 * time.Second) } @@ -969,3 +999,51 @@ func (n *Node) StopAndWait() { log.Error("error on stack close", "err", err) } } + +func (n *Node) FetchBatch(ctx context.Context, batchNum uint64) ([]byte, common.Hash, error) { + return n.InboxReader.GetSequencerMessageBytes(ctx, batchNum) +} + +func (n *Node) FindInboxBatchContainingMessage(message arbutil.MessageIndex) (uint64, bool, error) { + return n.InboxTracker.FindInboxBatchContainingMessage(message) +} + +func (n *Node) GetBatchParentChainBlock(seqNum uint64) (uint64, error) { + return n.InboxTracker.GetBatchParentChainBlock(seqNum) +} + +func (n *Node) FullSyncProgressMap() map[string]interface{} { + return n.SyncMonitor.FullSyncProgressMap() +} + +func (n *Node) Synced() bool { + return n.SyncMonitor.Synced() +} + +func (n *Node) SyncTargetMessageCount() arbutil.MessageIndex { + return n.SyncMonitor.SyncTargetMessageCount() +} + +// TODO: switch from pulling to pushing safe/finalized +func (n *Node) GetSafeMsgCount(ctx context.Context) (arbutil.MessageIndex, error) { + return n.InboxReader.GetSafeMsgCount(ctx) +} + +func (n *Node) GetFinalizedMsgCount(ctx context.Context) (arbutil.MessageIndex, error) { + return n.InboxReader.GetFinalizedMsgCount(ctx) +} + +func (n *Node) WriteMessageFromSequencer(pos arbutil.MessageIndex, msgWithMeta arbostypes.MessageWithMetadata, msgResult execution.MessageResult) error { + return n.TxStreamer.WriteMessageFromSequencer(pos, msgWithMeta, msgResult) +} + +func (n *Node) ExpectChosenSequencer() error { + return n.TxStreamer.ExpectChosenSequencer() +} + +func (n *Node) ValidatedMessageCount() (arbutil.MessageIndex, error) { + if n.BlockValidator == nil { + return 0, errors.New("validator not set up") + } + return n.BlockValidator.GetValidated(), nil +} diff --git a/arbnode/schema.go b/arbnode/schema.go index ddc7cf54f..2854b7e78 100644 --- a/arbnode/schema.go +++ b/arbnode/schema.go @@ -5,6 +5,7 @@ package arbnode var ( messagePrefix []byte = []byte("m") // maps a message sequence number to a message + blockHashInputFeedPrefix []byte = []byte("b") // maps a message sequence number to a block hash received through the input feed legacyDelayedMessagePrefix []byte = []byte("d") // maps a delayed sequence number to an accumulator and a message as serialized on L1 rlpDelayedMessagePrefix []byte = []byte("e") // maps a delayed sequence number to an accumulator and an RLP encoded message parentChainBlockNumberPrefix []byte = []byte("p") // maps a delayed sequence number to a parent chain block number diff --git a/arbnode/sequencer_inbox.go b/arbnode/sequencer_inbox.go index e66fba1d3..aae440a61 100644 --- a/arbnode/sequencer_inbox.go +++ b/arbnode/sequencer_inbox.go @@ -16,7 +16,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/eigenda" @@ -163,7 +163,7 @@ func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client arbut if len(tx.BlobHashes()) == 0 { return nil, fmt.Errorf("blob batch transaction %v has no blobs", tx.Hash()) } - data := []byte{arbstate.BlobHashesHeaderFlag} + data := []byte{daprovider.BlobHashesHeaderFlag} for _, h := range tx.BlobHashes() { data = append(data, h[:]...) } diff --git a/arbnode/sync_monitor.go b/arbnode/sync_monitor.go index 99a66abde..d3b9a7e1c 100644 --- a/arbnode/sync_monitor.go +++ b/arbnode/sync_monitor.go @@ -2,120 +2,146 @@ package arbnode import ( "context" - "errors" - "sync/atomic" + "sync" + "time" + "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/util/stopwaiter" flag "github.com/spf13/pflag" ) type SyncMonitor struct { - config *SyncMonitorConfig + stopwaiter.StopWaiter + config func() *SyncMonitorConfig inboxReader *InboxReader txStreamer *TransactionStreamer coordinator *SeqCoordinator - exec execution.FullExecutionClient initialized bool + + syncTargetLock sync.Mutex + nextSyncTarget arbutil.MessageIndex + syncTarget arbutil.MessageIndex } -func NewSyncMonitor(config *SyncMonitorConfig) *SyncMonitor { +func NewSyncMonitor(config func() *SyncMonitorConfig) *SyncMonitor { return &SyncMonitor{ config: config, } } type SyncMonitorConfig struct { - BlockBuildLag uint64 `koanf:"block-build-lag"` - BlockBuildSequencerInboxLag uint64 `koanf:"block-build-sequencer-inbox-lag"` - CoordinatorMsgLag uint64 `koanf:"coordinator-msg-lag"` - SafeBlockWaitForBlockValidator bool `koanf:"safe-block-wait-for-block-validator"` - FinalizedBlockWaitForBlockValidator bool `koanf:"finalized-block-wait-for-block-validator"` + MsgLag time.Duration `koanf:"msg-lag"` } var DefaultSyncMonitorConfig = SyncMonitorConfig{ - BlockBuildLag: 20, - BlockBuildSequencerInboxLag: 0, - CoordinatorMsgLag: 15, - SafeBlockWaitForBlockValidator: false, - FinalizedBlockWaitForBlockValidator: false, + MsgLag: time.Second, +} + +var TestSyncMonitorConfig = SyncMonitorConfig{ + MsgLag: time.Millisecond * 10, } func SyncMonitorConfigAddOptions(prefix string, f *flag.FlagSet) { - f.Uint64(prefix+".block-build-lag", DefaultSyncMonitorConfig.BlockBuildLag, "allowed lag between messages read and blocks built") - f.Uint64(prefix+".block-build-sequencer-inbox-lag", DefaultSyncMonitorConfig.BlockBuildSequencerInboxLag, "allowed lag between messages read from sequencer inbox and blocks built") - f.Uint64(prefix+".coordinator-msg-lag", DefaultSyncMonitorConfig.CoordinatorMsgLag, "allowed lag between local and remote messages") - f.Bool(prefix+".safe-block-wait-for-block-validator", DefaultSyncMonitorConfig.SafeBlockWaitForBlockValidator, "wait for block validator to complete before returning safe block number") - f.Bool(prefix+".finalized-block-wait-for-block-validator", DefaultSyncMonitorConfig.FinalizedBlockWaitForBlockValidator, "wait for block validator to complete before returning finalized block number") + f.Duration(prefix+".msg-lag", DefaultSyncMonitorConfig.MsgLag, "allowed msg lag while still considered in sync") } -func (s *SyncMonitor) Initialize(inboxReader *InboxReader, txStreamer *TransactionStreamer, coordinator *SeqCoordinator, exec execution.FullExecutionClient) { +func (s *SyncMonitor) Initialize(inboxReader *InboxReader, txStreamer *TransactionStreamer, coordinator *SeqCoordinator) { s.inboxReader = inboxReader s.txStreamer = txStreamer s.coordinator = coordinator - s.exec = exec s.initialized = true } -func (s *SyncMonitor) SyncProgressMap() map[string]interface{} { - syncing := false - res := make(map[string]interface{}) +func (s *SyncMonitor) updateSyncTarget(ctx context.Context) time.Duration { + nextSyncTarget, err := s.maxMessageCount() + if err != nil { + log.Warn("failed readin max msg count", "err", err) + return s.config().MsgLag + } + s.syncTargetLock.Lock() + defer s.syncTargetLock.Unlock() + s.syncTarget = s.nextSyncTarget + s.nextSyncTarget = nextSyncTarget + return s.config().MsgLag +} - if !s.initialized { - res["err"] = "uninitialized" - return res +func (s *SyncMonitor) SyncTargetMessageCount() arbutil.MessageIndex { + s.syncTargetLock.Lock() + defer s.syncTargetLock.Unlock() + return s.syncTarget +} + +func (s *SyncMonitor) maxMessageCount() (arbutil.MessageIndex, error) { + msgCount, err := s.txStreamer.GetMessageCount() + if err != nil { + return 0, err } - broadcasterQueuedMessagesPos := atomic.LoadUint64(&(s.txStreamer.broadcasterQueuedMessagesPos)) + pending := s.txStreamer.FeedPendingMessageCount() + if pending > msgCount { + msgCount = pending + } - if broadcasterQueuedMessagesPos != 0 { // unprocessed feed - syncing = true + if s.inboxReader != nil { + batchProcessed := s.inboxReader.GetLastReadBatchCount() + + if batchProcessed > 0 { + batchMsgCount, err := s.inboxReader.Tracker().GetBatchMessageCount(batchProcessed - 1) + if err != nil { + return msgCount, err + } + if batchMsgCount > msgCount { + msgCount = batchMsgCount + } + } } - res["broadcasterQueuedMessagesPos"] = broadcasterQueuedMessagesPos - builtMessageCount, err := s.exec.HeadMessageNumber() - if err != nil { - res["builtMessageCountError"] = err.Error() - syncing = true - builtMessageCount = 0 - } else { - blockNum := s.exec.MessageIndexToBlockNumber(builtMessageCount) - res["blockNum"] = blockNum - builtMessageCount++ - res["messageOfLastBlock"] = builtMessageCount + if s.coordinator != nil { + coordinatorMessageCount, err := s.coordinator.GetRemoteMsgCount() //NOTE: this creates a remote call + if err != nil { + return msgCount, err + } + if coordinatorMessageCount > msgCount { + msgCount = coordinatorMessageCount + } } + return msgCount, nil +} + +func (s *SyncMonitor) FullSyncProgressMap() map[string]interface{} { + res := make(map[string]interface{}) + + if !s.initialized { + res["err"] = "uninitialized" + return res + } + + syncTarget := s.SyncTargetMessageCount() + res["syncTargetMsgCount"] = syncTarget + msgCount, err := s.txStreamer.GetMessageCount() if err != nil { res["msgCountError"] = err.Error() - syncing = true - } else { - res["msgCount"] = msgCount - if builtMessageCount+arbutil.MessageIndex(s.config.BlockBuildLag) < msgCount { - syncing = true - } + return res } + res["msgCount"] = msgCount + + res["feedPendingMessageCount"] = s.txStreamer.FeedPendingMessageCount() if s.inboxReader != nil { batchSeen := s.inboxReader.GetLastSeenBatchCount() - _, batchProcessed := s.inboxReader.GetLastReadBlockAndBatchCount() - - if (batchSeen == 0) || // error or not yet read inbox - (batchProcessed < batchSeen) { // unprocessed inbox messages - syncing = true - } res["batchSeen"] = batchSeen + + batchProcessed := s.inboxReader.GetLastReadBatchCount() res["batchProcessed"] = batchProcessed - processedMetadata, err := s.inboxReader.Tracker().GetBatchMetadata(batchProcessed - 1) + processedBatchMsgs, err := s.inboxReader.Tracker().GetBatchMessageCount(batchProcessed - 1) if err != nil { res["batchMetadataError"] = err.Error() - syncing = true } else { - res["messageOfProcessedBatch"] = processedMetadata.MessageCount - if builtMessageCount+arbutil.MessageIndex(s.config.BlockBuildSequencerInboxLag) < processedMetadata.MessageCount { - syncing = true - } + res["messageOfProcessedBatch"] = processedBatchMsgs } l1reader := s.inboxReader.l1Reader @@ -135,73 +161,55 @@ func (s *SyncMonitor) SyncProgressMap() map[string]interface{} { coordinatorMessageCount, err := s.coordinator.GetRemoteMsgCount() //NOTE: this creates a remote call if err != nil { res["coordinatorMsgCountError"] = err.Error() - syncing = true } else { res["coordinatorMessageCount"] = coordinatorMessageCount - if msgCount+arbutil.MessageIndex(s.config.CoordinatorMsgLag) < coordinatorMessageCount { - syncing = true - } } } - if !syncing { - return make(map[string]interface{}) - } - return res } -func (s *SyncMonitor) SafeBlockNumber(ctx context.Context) (uint64, error) { - if s.inboxReader == nil || !s.initialized { - return 0, errors.New("not set up for safeblock") - } - msg, err := s.inboxReader.GetSafeMsgCount(ctx) - if err != nil { - return 0, err - } - // If SafeBlockWaitForBlockValidator is true, we want to wait for the block validator to finish - if s.config.SafeBlockWaitForBlockValidator { - latestValidatedCount, err := s.getLatestValidatedCount() - if err != nil { - return 0, err - } - if msg > latestValidatedCount { - msg = latestValidatedCount - } +func (s *SyncMonitor) SyncProgressMap() map[string]interface{} { + if s.Synced() { + return make(map[string]interface{}) } - block := s.exec.MessageIndexToBlockNumber(msg - 1) - return block, nil + + return s.FullSyncProgressMap() } -func (s *SyncMonitor) getLatestValidatedCount() (arbutil.MessageIndex, error) { - if s.txStreamer.validator == nil { - return 0, errors.New("validator not set up") - } - return s.txStreamer.validator.GetValidated(), nil +func (s *SyncMonitor) Start(ctx_in context.Context) { + s.StopWaiter.Start(ctx_in, s) + s.CallIteratively(s.updateSyncTarget) } -func (s *SyncMonitor) FinalizedBlockNumber(ctx context.Context) (uint64, error) { - if s.inboxReader == nil || !s.initialized { - return 0, errors.New("not set up for safeblock") +func (s *SyncMonitor) Synced() bool { + if !s.initialized { + return false } - msg, err := s.inboxReader.GetFinalizedMsgCount(ctx) + if !s.Started() { + return false + } + syncTarget := s.SyncTargetMessageCount() + + msgCount, err := s.txStreamer.GetMessageCount() if err != nil { - return 0, err + return false } - // If FinalizedBlockWaitForBlockValidator is true, we want to wait for the block validator to finish - if s.config.FinalizedBlockWaitForBlockValidator { - latestValidatedCount, err := s.getLatestValidatedCount() - if err != nil { - return 0, err + + if syncTarget > msgCount { + return false + } + + if s.inboxReader != nil { + batchSeen := s.inboxReader.GetLastSeenBatchCount() + if batchSeen == 0 { + return false } - if msg > latestValidatedCount { - msg = latestValidatedCount + batchProcessed := s.inboxReader.GetLastReadBatchCount() + + if batchProcessed < batchSeen { + return false } } - block := s.exec.MessageIndexToBlockNumber(msg - 1) - return block, nil -} - -func (s *SyncMonitor) Synced() bool { - return len(s.SyncProgressMap()) == 0 + return true } diff --git a/arbnode/transaction_streamer.go b/arbnode/transaction_streamer.go index 7e9cf1dba..5c02129ee 100644 --- a/arbnode/transaction_streamer.go +++ b/arbnode/transaction_streamer.go @@ -50,9 +50,10 @@ type TransactionStreamer struct { execLastMsgCount arbutil.MessageIndex validator *staker.BlockValidator - db ethdb.Database - fatalErrChan chan<- error - config TransactionStreamerConfigFetcher + db ethdb.Database + fatalErrChan chan<- error + config TransactionStreamerConfigFetcher + snapSyncConfig *SnapSyncConfig insertionMutex sync.Mutex // cannot be acquired while reorgMutex is held reorgMutex sync.RWMutex @@ -60,7 +61,7 @@ type TransactionStreamer struct { nextAllowedFeedReorgLog time.Time - broadcasterQueuedMessages []arbostypes.MessageWithMetadata + broadcasterQueuedMessages []arbostypes.MessageWithMetadataAndBlockHash broadcasterQueuedMessagesPos uint64 broadcasterQueuedMessagesActiveReorg bool @@ -103,6 +104,7 @@ func NewTransactionStreamer( broadcastServer *broadcaster.Broadcaster, fatalErrChan chan<- error, config TransactionStreamerConfigFetcher, + snapSyncConfig *SnapSyncConfig, ) (*TransactionStreamer, error) { streamer := &TransactionStreamer{ exec: exec, @@ -112,8 +114,8 @@ func NewTransactionStreamer( broadcastServer: broadcastServer, fatalErrChan: fatalErrChan, config: config, + snapSyncConfig: snapSyncConfig, } - streamer.exec.SetTransactionStreamer(streamer) err := streamer.cleanupInconsistentState() if err != nil { return nil, err @@ -121,6 +123,16 @@ func NewTransactionStreamer( return streamer, nil } +// Represents a block's hash in the database. +// Necessary because RLP decoder doesn't produce nil values by default. +type blockHashDBValue struct { + BlockHash *common.Hash `rlp:"nil"` +} + +const ( + BlockHashMismatchLogMsg = "BlockHash from feed doesn't match locally computed hash. Check feed source." +) + // Encodes a uint64 as bytes in a lexically sortable manner for database iteration. // Generally this is only used for database keys, which need sorted. // A shorter RLP encoding is usually used for database values. @@ -161,6 +173,10 @@ func (s *TransactionStreamer) SetInboxReaders(inboxReader *InboxReader, delayedB s.delayedBridge = delayedBridge } +func (s *TransactionStreamer) ChainConfig() *params.ChainConfig { + return s.chainConfig +} + func (s *TransactionStreamer) cleanupInconsistentState() error { // If it doesn't exist yet, set the message count to 0 hasMessageCount, err := s.db.Has(messageCountKey) @@ -248,7 +264,7 @@ func deleteFromRange(ctx context.Context, db ethdb.Database, prefix []byte, star // The insertion mutex must be held. This acquires the reorg mutex. // Note: oldMessages will be empty if reorgHook is nil -func (s *TransactionStreamer) reorg(batch ethdb.Batch, count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadata) error { +func (s *TransactionStreamer) reorg(batch ethdb.Batch, count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadataAndBlockHash) error { if count == 0 { return errors.New("cannot reorg out init message") } @@ -337,11 +353,20 @@ func (s *TransactionStreamer) reorg(batch ethdb.Batch, count arbutil.MessageInde s.reorgMutex.Lock() defer s.reorgMutex.Unlock() - err = s.exec.Reorg(count, newMessages, oldMessages) + messagesResults, err := s.exec.Reorg(count, newMessages, oldMessages) if err != nil { return err } + messagesWithComputedBlockHash := make([]arbostypes.MessageWithMetadataAndBlockHash, 0, len(messagesResults)) + for i := 0; i < len(messagesResults); i++ { + messagesWithComputedBlockHash = append(messagesWithComputedBlockHash, arbostypes.MessageWithMetadataAndBlockHash{ + MessageWithMeta: newMessages[i].MessageWithMeta, + BlockHash: &messagesResults[i].BlockHash, + }) + } + s.broadcastMessages(messagesWithComputedBlockHash, count) + if s.validator != nil { err = s.validator.Reorg(s.GetContext(), count) if err != nil { @@ -349,6 +374,10 @@ func (s *TransactionStreamer) reorg(batch ethdb.Batch, count arbutil.MessageInde } } + err = deleteStartingAt(s.db, batch, blockHashInputFeedPrefix, uint64ToKey(uint64(count))) + if err != nil { + return err + } err = deleteStartingAt(s.db, batch, messagePrefix, uint64ToKey(uint64(count))) if err != nil { return err @@ -378,6 +407,10 @@ func dbKey(prefix []byte, pos uint64) []byte { return key } +func isErrNotFound(err error) bool { + return errors.Is(err, leveldb.ErrNotFound) || errors.Is(err, pebble.ErrNotFound) +} + // Note: if changed to acquire the mutex, some internal users may need to be updated to a non-locking version. func (s *TransactionStreamer) GetMessage(seqNum arbutil.MessageIndex) (*arbostypes.MessageWithMetadata, error) { key := dbKey(messagePrefix, uint64(seqNum)) @@ -394,6 +427,36 @@ func (s *TransactionStreamer) GetMessage(seqNum arbutil.MessageIndex) (*arbostyp return &message, nil } +func (s *TransactionStreamer) getMessageWithMetadataAndBlockHash(seqNum arbutil.MessageIndex) (*arbostypes.MessageWithMetadataAndBlockHash, error) { + msg, err := s.GetMessage(seqNum) + if err != nil { + return nil, err + } + + // Get block hash. + // To keep it backwards compatible, since it is possible that a message related + // to a sequence number exists in the database, but the block hash doesn't. + key := dbKey(blockHashInputFeedPrefix, uint64(seqNum)) + var blockHash *common.Hash + data, err := s.db.Get(key) + if err == nil { + var blockHashDBVal blockHashDBValue + err = rlp.DecodeBytes(data, &blockHashDBVal) + if err != nil { + return nil, err + } + blockHash = blockHashDBVal.BlockHash + } else if !isErrNotFound(err) { + return nil, err + } + + msgWithBlockHash := arbostypes.MessageWithMetadataAndBlockHash{ + MessageWithMeta: *msg, + BlockHash: blockHash, + } + return &msgWithBlockHash, nil +} + // Note: if changed to acquire the mutex, some internal users may need to be updated to a non-locking version. func (s *TransactionStreamer) GetMessageCount() (arbutil.MessageIndex, error) { posBytes, err := s.db.Get(messageCountKey) @@ -427,12 +490,27 @@ func (s *TransactionStreamer) AddMessages(pos arbutil.MessageIndex, messagesAreC return s.AddMessagesAndEndBatch(pos, messagesAreConfirmed, messages, nil) } +func (s *TransactionStreamer) FeedPendingMessageCount() arbutil.MessageIndex { + pos := atomic.LoadUint64(&s.broadcasterQueuedMessagesPos) + if pos == 0 { + return 0 + } + + s.insertionMutex.Lock() + defer s.insertionMutex.Unlock() + pos = atomic.LoadUint64(&s.broadcasterQueuedMessagesPos) + if pos == 0 { + return 0 + } + return arbutil.MessageIndex(pos + uint64(len(s.broadcasterQueuedMessages))) +} + func (s *TransactionStreamer) AddBroadcastMessages(feedMessages []*m.BroadcastFeedMessage) error { if len(feedMessages) == 0 { return nil } broadcastStartPos := feedMessages[0].SequenceNumber - var messages []arbostypes.MessageWithMetadata + var messages []arbostypes.MessageWithMetadataAndBlockHash broadcastAfterPos := broadcastStartPos for _, feedMessage := range feedMessages { if broadcastAfterPos != feedMessage.SequenceNumber { @@ -441,7 +519,11 @@ func (s *TransactionStreamer) AddBroadcastMessages(feedMessages []*m.BroadcastFe if feedMessage.Message.Message == nil || feedMessage.Message.Message.Header == nil { return fmt.Errorf("invalid feed message at sequence number %v", feedMessage.SequenceNumber) } - messages = append(messages, feedMessage.Message) + msgWithBlockHash := arbostypes.MessageWithMetadataAndBlockHash{ + MessageWithMeta: feedMessage.Message, + BlockHash: feedMessage.BlockHash, + } + messages = append(messages, msgWithBlockHash) broadcastAfterPos++ } @@ -460,7 +542,7 @@ func (s *TransactionStreamer) AddBroadcastMessages(feedMessages []*m.BroadcastFe messages = messages[dups:] broadcastStartPos += arbutil.MessageIndex(dups) if oldMsg != nil { - s.logReorg(broadcastStartPos, oldMsg, &messages[0], false) + s.logReorg(broadcastStartPos, oldMsg, &messages[0].MessageWithMeta, false) } if len(messages) == 0 { // No new messages received @@ -510,7 +592,7 @@ func (s *TransactionStreamer) AddBroadcastMessages(feedMessages []*m.BroadcastFe if broadcastStartPos > 0 { _, err := s.GetMessage(broadcastStartPos - 1) if err != nil { - if !errors.Is(err, leveldb.ErrNotFound) && !errors.Is(err, pebble.ErrNotFound) { + if !isErrNotFound(err) { return err } // Message before current message doesn't exist in database, so don't add current messages yet @@ -562,9 +644,18 @@ func endBatch(batch ethdb.Batch) error { } func (s *TransactionStreamer) AddMessagesAndEndBatch(pos arbutil.MessageIndex, messagesAreConfirmed bool, messages []arbostypes.MessageWithMetadata, batch ethdb.Batch) error { + messagesWithBlockHash := make([]arbostypes.MessageWithMetadataAndBlockHash, 0, len(messages)) + for _, message := range messages { + messagesWithBlockHash = append(messagesWithBlockHash, arbostypes.MessageWithMetadataAndBlockHash{ + MessageWithMeta: message, + }) + } + if messagesAreConfirmed { + // Trim confirmed messages from l1pricedataCache + s.exec.MarkFeedStart(pos + arbutil.MessageIndex(len(messages))) s.reorgMutex.RLock() - dups, _, _, err := s.countDuplicateMessages(pos, messages, nil) + dups, _, _, err := s.countDuplicateMessages(pos, messagesWithBlockHash, nil) s.reorgMutex.RUnlock() if err != nil { return err @@ -581,10 +672,13 @@ func (s *TransactionStreamer) AddMessagesAndEndBatch(pos arbutil.MessageIndex, m s.insertionMutex.Lock() defer s.insertionMutex.Unlock() - return s.addMessagesAndEndBatchImpl(pos, messagesAreConfirmed, messages, batch) + return s.addMessagesAndEndBatchImpl(pos, messagesAreConfirmed, messagesWithBlockHash, batch) } func (s *TransactionStreamer) getPrevPrevDelayedRead(pos arbutil.MessageIndex) (uint64, error) { + if s.snapSyncConfig.Enabled && uint64(pos) == s.snapSyncConfig.PrevBatchMessageCount { + return s.snapSyncConfig.PrevDelayedRead, nil + } var prevDelayedRead uint64 if pos > 0 { prevMsg, err := s.GetMessage(pos - 1) @@ -599,7 +693,7 @@ func (s *TransactionStreamer) getPrevPrevDelayedRead(pos arbutil.MessageIndex) ( func (s *TransactionStreamer) countDuplicateMessages( pos arbutil.MessageIndex, - messages []arbostypes.MessageWithMetadata, + messages []arbostypes.MessageWithMetadataAndBlockHash, batch *ethdb.Batch, ) (int, bool, *arbostypes.MessageWithMetadata, error) { curMsg := 0 @@ -620,7 +714,7 @@ func (s *TransactionStreamer) countDuplicateMessages( return 0, false, nil, err } nextMessage := messages[curMsg] - wantMessage, err := rlp.EncodeToBytes(nextMessage) + wantMessage, err := rlp.EncodeToBytes(nextMessage.MessageWithMeta) if err != nil { return 0, false, nil, err } @@ -636,12 +730,12 @@ func (s *TransactionStreamer) countDuplicateMessages( return curMsg, true, nil, nil } var duplicateMessage bool - if nextMessage.Message != nil { - if dbMessageParsed.Message.BatchGasCost == nil || nextMessage.Message.BatchGasCost == nil { + if nextMessage.MessageWithMeta.Message != nil { + if dbMessageParsed.Message.BatchGasCost == nil || nextMessage.MessageWithMeta.Message.BatchGasCost == nil { // Remove both of the batch gas costs and see if the messages still differ - nextMessageCopy := nextMessage + nextMessageCopy := nextMessage.MessageWithMeta nextMessageCopy.Message = new(arbostypes.L1IncomingMessage) - *nextMessageCopy.Message = *nextMessage.Message + *nextMessageCopy.Message = *nextMessage.MessageWithMeta.Message batchGasCostBkup := dbMessageParsed.Message.BatchGasCost dbMessageParsed.Message.BatchGasCost = nil nextMessageCopy.Message.BatchGasCost = nil @@ -649,7 +743,7 @@ func (s *TransactionStreamer) countDuplicateMessages( // Actually this isn't a reorg; only the batch gas costs differed duplicateMessage = true // If possible - update the message in the database to add the gas cost cache. - if batch != nil && nextMessage.Message.BatchGasCost != nil { + if batch != nil && nextMessage.MessageWithMeta.Message.BatchGasCost != nil { if *batch == nil { *batch = s.db.NewBatch() } @@ -693,7 +787,7 @@ func (s *TransactionStreamer) logReorg(pos arbutil.MessageIndex, dbMsg *arbostyp } -func (s *TransactionStreamer) addMessagesAndEndBatchImpl(messageStartPos arbutil.MessageIndex, messagesAreConfirmed bool, messages []arbostypes.MessageWithMetadata, batch ethdb.Batch) error { +func (s *TransactionStreamer) addMessagesAndEndBatchImpl(messageStartPos arbutil.MessageIndex, messagesAreConfirmed bool, messages []arbostypes.MessageWithMetadataAndBlockHash, batch ethdb.Batch) error { var confirmedReorg bool var oldMsg *arbostypes.MessageWithMetadata var lastDelayedRead uint64 @@ -711,7 +805,7 @@ func (s *TransactionStreamer) addMessagesAndEndBatchImpl(messageStartPos arbutil return err } if duplicates > 0 { - lastDelayedRead = messages[duplicates-1].DelayedMessagesRead + lastDelayedRead = messages[duplicates-1].MessageWithMeta.DelayedMessagesRead messages = messages[duplicates:] messageStartPos += arbutil.MessageIndex(duplicates) } @@ -749,13 +843,13 @@ func (s *TransactionStreamer) addMessagesAndEndBatchImpl(messageStartPos arbutil return err } if duplicates > 0 { - lastDelayedRead = messages[duplicates-1].DelayedMessagesRead + lastDelayedRead = messages[duplicates-1].MessageWithMeta.DelayedMessagesRead messages = messages[duplicates:] messageStartPos += arbutil.MessageIndex(duplicates) } } if oldMsg != nil { - s.logReorg(messageStartPos, oldMsg, &messages[0], confirmedReorg) + s.logReorg(messageStartPos, oldMsg, &messages[0].MessageWithMeta, confirmedReorg) } if feedReorg { @@ -775,12 +869,12 @@ func (s *TransactionStreamer) addMessagesAndEndBatchImpl(messageStartPos arbutil // Validate delayed message counts of remaining messages for i, msg := range messages { msgPos := messageStartPos + arbutil.MessageIndex(i) - diff := msg.DelayedMessagesRead - lastDelayedRead + diff := msg.MessageWithMeta.DelayedMessagesRead - lastDelayedRead if diff != 0 && diff != 1 { - return fmt.Errorf("attempted to insert jump from %v delayed messages read to %v delayed messages read at message index %v", lastDelayedRead, msg.DelayedMessagesRead, msgPos) + return fmt.Errorf("attempted to insert jump from %v delayed messages read to %v delayed messages read at message index %v", lastDelayedRead, msg.MessageWithMeta.DelayedMessagesRead, msgPos) } - lastDelayedRead = msg.DelayedMessagesRead - if msg.Message == nil { + lastDelayedRead = msg.MessageWithMeta.DelayedMessagesRead + if msg.MessageWithMeta.Message == nil { return fmt.Errorf("attempted to insert nil message at position %v", msgPos) } } @@ -820,10 +914,6 @@ func (s *TransactionStreamer) addMessagesAndEndBatchImpl(messageStartPos arbutil return nil } -func (s *TransactionStreamer) FetchBatch(batchNum uint64) ([]byte, common.Hash, error) { - return s.inboxReader.GetSequencerMessageBytes(context.TODO(), batchNum) -} - // The caller must hold the insertionMutex func (s *TransactionStreamer) ExpectChosenSequencer() error { if s.coordinator != nil { @@ -834,7 +924,11 @@ func (s *TransactionStreamer) ExpectChosenSequencer() error { return nil } -func (s *TransactionStreamer) WriteMessageFromSequencer(pos arbutil.MessageIndex, msgWithMeta arbostypes.MessageWithMetadata) error { +func (s *TransactionStreamer) WriteMessageFromSequencer( + pos arbutil.MessageIndex, + msgWithMeta arbostypes.MessageWithMetadata, + msgResult execution.MessageResult, +) error { if err := s.ExpectChosenSequencer(); err != nil { return err } @@ -858,17 +952,19 @@ func (s *TransactionStreamer) WriteMessageFromSequencer(pos arbutil.MessageIndex } } - if err := s.writeMessages(pos, []arbostypes.MessageWithMetadata{msgWithMeta}, nil); err != nil { + msgWithBlockHash := arbostypes.MessageWithMetadataAndBlockHash{ + MessageWithMeta: msgWithMeta, + BlockHash: &msgResult.BlockHash, + } + + if err := s.writeMessages(pos, []arbostypes.MessageWithMetadataAndBlockHash{msgWithBlockHash}, nil); err != nil { return err } + s.broadcastMessages([]arbostypes.MessageWithMetadataAndBlockHash{msgWithBlockHash}, pos) return nil } -func (s *TransactionStreamer) GenesisBlockNumber() uint64 { - return s.chainConfig.ArbitrumChainParams.GenesisBlockNum -} - // PauseReorgs until a matching call to ResumeReorgs (may be called concurrently) func (s *TransactionStreamer) PauseReorgs() { s.reorgMutex.RLock() @@ -885,18 +981,44 @@ func (s *TransactionStreamer) PopulateFeedBacklog() error { return s.inboxReader.tracker.PopulateFeedBacklog(s.broadcastServer) } -func (s *TransactionStreamer) writeMessage(pos arbutil.MessageIndex, msg arbostypes.MessageWithMetadata, batch ethdb.Batch) error { +func (s *TransactionStreamer) writeMessage(pos arbutil.MessageIndex, msg arbostypes.MessageWithMetadataAndBlockHash, batch ethdb.Batch) error { + // write message with metadata key := dbKey(messagePrefix, uint64(pos)) - msgBytes, err := rlp.EncodeToBytes(msg) + msgBytes, err := rlp.EncodeToBytes(msg.MessageWithMeta) + if err != nil { + return err + } + if err := batch.Put(key, msgBytes); err != nil { + return err + } + + // write block hash + blockHashDBVal := blockHashDBValue{ + BlockHash: msg.BlockHash, + } + key = dbKey(blockHashInputFeedPrefix, uint64(pos)) + msgBytes, err = rlp.EncodeToBytes(blockHashDBVal) if err != nil { return err } return batch.Put(key, msgBytes) } +func (s *TransactionStreamer) broadcastMessages( + msgs []arbostypes.MessageWithMetadataAndBlockHash, + pos arbutil.MessageIndex, +) { + if s.broadcastServer == nil { + return + } + if err := s.broadcastServer.BroadcastMessages(msgs, pos); err != nil { + log.Error("failed broadcasting messages", "pos", pos, "err", err) + } +} + // The mutex must be held, and pos must be the latest message count. // `batch` may be nil, which initializes a new batch. The batch is closed out in this function. -func (s *TransactionStreamer) writeMessages(pos arbutil.MessageIndex, messages []arbostypes.MessageWithMetadata, batch ethdb.Batch) error { +func (s *TransactionStreamer) writeMessages(pos arbutil.MessageIndex, messages []arbostypes.MessageWithMetadataAndBlockHash, batch ethdb.Batch) error { if batch == nil { batch = s.db.NewBatch() } @@ -921,12 +1043,6 @@ func (s *TransactionStreamer) writeMessages(pos arbutil.MessageIndex, messages [ default: } - if s.broadcastServer != nil { - if err := s.broadcastServer.BroadcastMessages(messages, pos); err != nil { - log.Error("failed broadcasting message", "pos", pos, "err", err) - } - } - return nil } @@ -938,8 +1054,23 @@ func (s *TransactionStreamer) ResultAtCount(count arbutil.MessageIndex) (*execut return s.exec.ResultAtPos(count - 1) } +func (s *TransactionStreamer) checkResult(msgResult *execution.MessageResult, expectedBlockHash *common.Hash) { + if expectedBlockHash == nil { + return + } + if msgResult.BlockHash != *expectedBlockHash { + log.Error( + BlockHashMismatchLogMsg, + "expected", expectedBlockHash, + "actual", msgResult.BlockHash, + ) + return + } +} + +// exposed for testing // return value: true if should be called again immediately -func (s *TransactionStreamer) executeNextMsg(ctx context.Context, exec execution.ExecutionSequencer) bool { +func (s *TransactionStreamer) ExecuteNextMsg(ctx context.Context, exec execution.ExecutionSequencer) bool { if ctx.Err() != nil { return false } @@ -963,7 +1094,7 @@ func (s *TransactionStreamer) executeNextMsg(ctx context.Context, exec execution if pos >= msgCount { return false } - msg, err := s.GetMessage(pos) + msgAndBlockHash, err := s.getMessageWithMetadataAndBlockHash(pos) if err != nil { log.Error("feedOneMsg failed to readMessage", "err", err, "pos", pos) return false @@ -977,7 +1108,8 @@ func (s *TransactionStreamer) executeNextMsg(ctx context.Context, exec execution } msgForPrefetch = msg } - if err = s.exec.DigestMessage(pos, msg, msgForPrefetch); err != nil { + msgResult, err := s.exec.DigestMessage(pos, &msgAndBlockHash.MessageWithMeta, msgForPrefetch) + if err != nil { logger := log.Warn if prevMessageCount < msgCount { logger = log.Debug @@ -985,11 +1117,20 @@ func (s *TransactionStreamer) executeNextMsg(ctx context.Context, exec execution logger("feedOneMsg failed to send message to execEngine", "err", err, "pos", pos) return false } + + s.checkResult(msgResult, msgAndBlockHash.BlockHash) + + msgWithBlockHash := arbostypes.MessageWithMetadataAndBlockHash{ + MessageWithMeta: msgAndBlockHash.MessageWithMeta, + BlockHash: &msgResult.BlockHash, + } + s.broadcastMessages([]arbostypes.MessageWithMetadataAndBlockHash{msgWithBlockHash}, pos) + return pos+1 < msgCount } func (s *TransactionStreamer) executeMessages(ctx context.Context, ignored struct{}) time.Duration { - if s.executeNextMsg(ctx, s.exec) { + if s.ExecuteNextMsg(ctx, s.exec) { return 0 } return s.config().ExecuteMessageLoopDelay diff --git a/arbos/activate_test.go b/arbos/activate_test.go new file mode 100644 index 000000000..55440bb20 --- /dev/null +++ b/arbos/activate_test.go @@ -0,0 +1,106 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbos + +import ( + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/offchainlabs/nitro/arbos/programs" + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/colors" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func TestActivationDataFee(t *testing.T) { + rand.Seed(time.Now().UTC().UnixNano()) + state, _ := arbosState.NewArbosMemoryBackedArbOSState() + pricer := state.Programs().DataPricer() + time := uint64(time.Now().Unix()) + + assert := func(cond bool) { + t.Helper() + if !cond { + Fail(t, "assertion failed") + } + } + + hour := uint64(60 * 60) + commonSize := uint32(5 * 1024 * 1024) + + fee, _ := pricer.UpdateModel(0, time) + assert(fee.Uint64() == 0) + + firstHourlyFee, _ := pricer.UpdateModel(commonSize, time) + assert(firstHourlyFee.Uint64() > 0) + + capacity := uint32(programs.InitialHourlyBytes) + usage := uint32(0) + lastFee := common.Big0 + totalFees := common.Big0 + reset := func() { + capacity = uint32(programs.InitialHourlyBytes) + usage = uint32(0) + lastFee = common.Big0 + totalFees = common.Big0 + } + + reset() + for usage < capacity { + bytes := uint32(5 * 1024 * 1024) + fee, _ := pricer.UpdateModel(bytes, time+hour) + assert(arbmath.BigGreaterThan(fee, lastFee)) + + totalFees = arbmath.BigAdd(totalFees, fee) + usage += bytes + lastFee = fee + } + + // ensure the chain made enough money + minimumTotal := arbmath.UintToBig(uint64(capacity)) + minimumTotal = arbmath.BigMulByUint(minimumTotal, 59/10*1e9) + colors.PrintBlue("total ", totalFees.String(), " ", minimumTotal.String()) + assert(arbmath.BigGreaterThan(totalFees, minimumTotal)) + + // advance a bit past an hour to reset the pricer + fee, _ = pricer.UpdateModel(commonSize, time+2*hour+60) + assert(arbmath.BigEquals(fee, firstHourlyFee)) + + // buy all the capacity at once + fee, _ = pricer.UpdateModel(capacity, time+3*hour) + colors.PrintBlue("every ", fee.String(), " ", minimumTotal.String()) + assert(arbmath.BigGreaterThan(fee, minimumTotal)) + + reset() + for usage < capacity { + bytes := uint32(10 * 1024) + fee, _ := pricer.UpdateModel(bytes, time+5*hour) + assert(arbmath.BigGreaterThanOrEqual(fee, lastFee)) + + totalFees = arbmath.BigAdd(totalFees, fee) + usage += bytes + lastFee = fee + } + + // check small programs + colors.PrintBlue("small ", totalFees.String(), " ", minimumTotal.String()) + assert(arbmath.BigGreaterThan(totalFees, minimumTotal)) + + reset() + for usage < capacity { + bytes := testhelpers.RandomUint32(1, 1024*1024) + fee, _ := pricer.UpdateModel(bytes, time+7*hour) + + totalFees = arbmath.BigAdd(totalFees, fee) + usage += bytes + lastFee = fee + } + + // check random programs + colors.PrintBlue("rands ", totalFees.String(), " ", minimumTotal.String()) + assert(arbmath.BigGreaterThan(totalFees, minimumTotal)) +} diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 9e3b90532..0ac5d1380 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -25,6 +25,7 @@ import ( "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/merkleAccumulator" + "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbos/retryables" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/arbos/util" @@ -36,24 +37,27 @@ import ( // persisted beyond the end of the test.) type ArbosState struct { - arbosVersion uint64 // version of the ArbOS storage format and semantics - upgradeVersion storage.StorageBackedUint64 // version we're planning to upgrade to, or 0 if not planning to upgrade - upgradeTimestamp storage.StorageBackedUint64 // when to do the planned upgrade - networkFeeAccount storage.StorageBackedAddress - l1PricingState *l1pricing.L1PricingState - l2PricingState *l2pricing.L2PricingState - retryableState *retryables.RetryableState - addressTable *addressTable.AddressTable - chainOwners *addressSet.AddressSet - sendMerkle *merkleAccumulator.MerkleAccumulator - blockhashes *blockhash.Blockhashes - chainId storage.StorageBackedBigInt - chainConfig storage.StorageBackedBytes - genesisBlockNum storage.StorageBackedUint64 - infraFeeAccount storage.StorageBackedAddress - brotliCompressionLevel storage.StorageBackedUint64 // brotli compression level used for pricing - backingStorage *storage.Storage - Burner burn.Burner + arbosVersion uint64 // version of the ArbOS storage format and semantics + maxArbosVersionSupported uint64 // maximum ArbOS version supported by this code + maxDebugArbosVersionSupported uint64 // maximum ArbOS version supported by this code in debug mode + upgradeVersion storage.StorageBackedUint64 // version we're planning to upgrade to, or 0 if not planning to upgrade + upgradeTimestamp storage.StorageBackedUint64 // when to do the planned upgrade + networkFeeAccount storage.StorageBackedAddress + l1PricingState *l1pricing.L1PricingState + l2PricingState *l2pricing.L2PricingState + retryableState *retryables.RetryableState + addressTable *addressTable.AddressTable + chainOwners *addressSet.AddressSet + sendMerkle *merkleAccumulator.MerkleAccumulator + programs *programs.Programs + blockhashes *blockhash.Blockhashes + chainId storage.StorageBackedBigInt + chainConfig storage.StorageBackedBytes + genesisBlockNum storage.StorageBackedUint64 + infraFeeAccount storage.StorageBackedAddress + brotliCompressionLevel storage.StorageBackedUint64 // brotli compression level used for pricing + backingStorage *storage.Storage + Burner burn.Burner } var ErrUninitializedArbOS = errors.New("ArbOS uninitialized") @@ -70,6 +74,8 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error) } return &ArbosState{ arbosVersion, + 30, + 30, backingStorage.OpenStorageBackedUint64(uint64(upgradeVersionOffset)), backingStorage.OpenStorageBackedUint64(uint64(upgradeTimestampOffset)), backingStorage.OpenStorageBackedAddress(uint64(networkFeeAccountOffset)), @@ -79,6 +85,7 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error) addressTable.Open(backingStorage.OpenCachedSubStorage(addressTableSubspace)), addressSet.OpenAddressSet(backingStorage.OpenCachedSubStorage(chainOwnerSubspace)), merkleAccumulator.OpenMerkleAccumulator(backingStorage.OpenCachedSubStorage(sendMerkleSubspace)), + programs.Open(backingStorage.OpenSubStorage(programsSubspace)), blockhash.OpenBlockhashes(backingStorage.OpenCachedSubStorage(blockhashesSubspace)), backingStorage.OpenStorageBackedBigInt(uint64(chainIdOffset)), backingStorage.OpenStorageBackedBytes(chainConfigSubspace), @@ -156,32 +163,10 @@ var ( sendMerkleSubspace SubspaceID = []byte{5} blockhashesSubspace SubspaceID = []byte{6} chainConfigSubspace SubspaceID = []byte{7} + programsSubspace SubspaceID = []byte{8} ) -// Returns a list of precompiles that only appear in Arbitrum chains (i.e. ArbOS precompiles) at the genesis block -func getArbitrumOnlyGenesisPrecompiles(chainConfig *params.ChainConfig) []common.Address { - rules := chainConfig.Rules(big.NewInt(0), false, 0, chainConfig.ArbitrumChainParams.InitialArbOSVersion) - arbPrecompiles := vm.ActivePrecompiles(rules) - rules.IsArbitrum = false - ethPrecompiles := vm.ActivePrecompiles(rules) - - ethPrecompilesSet := make(map[common.Address]bool) - for _, addr := range ethPrecompiles { - ethPrecompilesSet[addr] = true - } - - var arbOnlyPrecompiles []common.Address - for _, addr := range arbPrecompiles { - if !ethPrecompilesSet[addr] { - arbOnlyPrecompiles = append(arbOnlyPrecompiles, addr) - } - } - return arbOnlyPrecompiles -} - -// During early development we sometimes change the storage format of version 1, for convenience. But as soon as we -// start running long-lived chains, every change to the storage format will require defining a new version and -// providing upgrade code. +var PrecompileMinArbOSVersions = make(map[common.Address]uint64) func InitializeArbosState(stateDB vm.StateDB, burner burn.Burner, chainConfig *params.ChainConfig, initMessage *arbostypes.ParsedInitMessage) (*ArbosState, error) { sto := storage.NewGeth(stateDB, burner) @@ -200,8 +185,10 @@ func InitializeArbosState(stateDB vm.StateDB, burner burn.Burner, chainConfig *p // Solidity requires call targets have code, but precompiles don't. // To work around this, we give precompiles fake code. - for _, genesisPrecompile := range getArbitrumOnlyGenesisPrecompiles(chainConfig) { - stateDB.SetCode(genesisPrecompile, []byte{byte(vm.INVALID)}) + for addr, version := range PrecompileMinArbOSVersions { + if version == 0 { + stateDB.SetCode(addr, []byte{byte(vm.INVALID)}) + } } // may be the zero address @@ -299,7 +286,8 @@ func (state *ArbosState) UpgradeArbosVersion( case 10: ensure(state.l1PricingState.SetL1FeesAvailable(stateDB.GetBalance( l1pricing.L1PricerFundsPoolAddress, - ))) + ).ToBig())) + case 11: // Update the PerBatchGasCost to a more accurate value compared to the old v6 default. ensure(state.l1PricingState.SetPerBatchGasCost(l1pricing.InitialPerBatchGasCostV12)) @@ -316,21 +304,35 @@ func (state *ArbosState) UpgradeArbosVersion( if !firstTime { ensure(state.chainOwners.ClearList()) } - // ArbOS versions 12 through 19 are left to Orbit chains for custom upgrades. + + case 12, 13, 14, 15, 16, 17, 18, 19: + // these versions are left to Orbit chains for custom upgrades. + case 20: // Update Brotli compression level for fast compression from 0 to 1 ensure(state.SetBrotliCompressionLevel(1)) + + case 21, 22, 23, 24, 25, 26, 27, 28, 29: + // these versions are left to Orbit chains for custom upgrades. + + case 30: + programs.Initialize(state.backingStorage.OpenSubStorage(programsSubspace)) + default: - if nextArbosVersion >= 12 && nextArbosVersion <= 19 { - // ArbOS versions 12 through 19 are left to Orbit chains for custom upgrades. - } else { - return fmt.Errorf( - "the chain is upgrading to unsupported ArbOS version %v, %w", - nextArbosVersion, - ErrFatalNodeOutOfDate, - ) + return fmt.Errorf( + "the chain is upgrading to unsupported ArbOS version %v, %w", + nextArbosVersion, + ErrFatalNodeOutOfDate, + ) + } + + // install any new precompiles + for addr, version := range PrecompileMinArbOSVersions { + if version == nextArbosVersion { + stateDB.SetCode(addr, []byte{byte(vm.INVALID)}) } } + state.arbosVersion = nextArbosVersion } @@ -400,6 +402,14 @@ func (state *ArbosState) RetryableState() *retryables.RetryableState { return state.retryableState } +func (state *ArbosState) MaxArbosVersionSupported() uint64 { + return state.maxArbosVersionSupported +} + +func (state *ArbosState) MaxDebugArbosVersionSupported() uint64 { + return state.maxDebugArbosVersionSupported +} + func (state *ArbosState) L1PricingState() *l1pricing.L1PricingState { return state.l1PricingState } @@ -423,6 +433,10 @@ func (state *ArbosState) SendMerkleAccumulator() *merkleAccumulator.MerkleAccumu return state.sendMerkle } +func (state *ArbosState) Programs() *programs.Programs { + return state.programs +} + func (state *ArbosState) Blockhashes() *blockhash.Blockhashes { return state.blockhashes } diff --git a/arbos/arbosState/initialization_test.go b/arbos/arbosState/initialization_test.go index 3de1fc5d3..0ef9cea4c 100644 --- a/arbos/arbosState/initialization_test.go +++ b/arbos/arbosState/initialization_test.go @@ -151,7 +151,7 @@ func checkAccounts(db *state.StateDB, arbState *ArbosState, accts []statetransfe if db.GetNonce(addr) != acct.Nonce { t.Fatal() } - if db.GetBalance(addr).Cmp(acct.EthBalance) != 0 { + if db.GetBalance(addr).ToBig().Cmp(acct.EthBalance) != 0 { t.Fatal() } if acct.ContractInfo != nil { diff --git a/arbos/arbosState/initialize.go b/arbos/arbosState/initialize.go index 56d8172ee..486c6ae33 100644 --- a/arbos/arbosState/initialize.go +++ b/arbos/arbosState/initialize.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/l2pricing" @@ -142,7 +143,7 @@ func InitializeArbosInDatabase(db ethdb.Database, initData statetransfer.InitDat if err != nil { return common.Hash{}, err } - statedb.SetBalance(account.Addr, account.EthBalance) + statedb.SetBalance(account.Addr, uint256.MustFromBig(account.EthBalance)) statedb.SetNonce(account.Addr, account.Nonce) if account.ContractInfo != nil { statedb.SetCode(account.Addr, account.ContractInfo.Code) @@ -173,7 +174,7 @@ func initializeRetryables(statedb *state.StateDB, rs *retryables.RetryableState, return err } if r.Timeout <= currentTimestamp { - statedb.AddBalance(r.Beneficiary, r.Callvalue) + statedb.AddBalance(r.Beneficiary, uint256.MustFromBig(r.Callvalue)) continue } retryablesList = append(retryablesList, r) @@ -192,7 +193,7 @@ func initializeRetryables(statedb *state.StateDB, rs *retryables.RetryableState, addr := r.To to = &addr } - statedb.AddBalance(retryables.RetryableEscrowAddress(r.Id), r.Callvalue) + statedb.AddBalance(retryables.RetryableEscrowAddress(r.Id), uint256.MustFromBig(r.Callvalue)) _, err := rs.CreateRetryable(r.Id, r.Timeout, r.From, to, r.Callvalue, r.Beneficiary, r.Calldata) if err != nil { return err diff --git a/arbos/arbostypes/incomingmessage.go b/arbos/arbostypes/incomingmessage.go index 1dc75c3e3..04ce8ebe2 100644 --- a/arbos/arbostypes/incomingmessage.go +++ b/arbos/arbostypes/incomingmessage.go @@ -34,8 +34,6 @@ const ( const MaxL2MessageSize = 256 * 1024 -const ArbosVersion_FixRedeemGas = uint64(11) - type L1IncomingMessageHeader struct { Kind uint8 `json:"kind"` Poster common.Address `json:"sender"` diff --git a/arbos/arbostypes/messagewithmeta.go b/arbos/arbostypes/messagewithmeta.go index a3d4f5e3c..79b7c4f9d 100644 --- a/arbos/arbostypes/messagewithmeta.go +++ b/arbos/arbostypes/messagewithmeta.go @@ -18,6 +18,11 @@ type MessageWithMetadata struct { DelayedMessagesRead uint64 `json:"delayedMessagesRead"` } +type MessageWithMetadataAndBlockHash struct { + MessageWithMeta MessageWithMetadata + BlockHash *common.Hash +} + var EmptyTestMessageWithMetadata = MessageWithMetadata{ Message: &EmptyTestIncomingMessage, } diff --git a/arbos/block_processor.go b/arbos/block_processor.go index f1838132a..9d6c420a5 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbos @@ -14,7 +14,6 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/util" - "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/arbitrum_types" @@ -25,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -40,7 +38,6 @@ var L2ToL1TransactionEventID common.Hash var L2ToL1TxEventID common.Hash var EmitReedeemScheduledEvent func(*vm.EVM, uint64, uint64, [32]byte, [32]byte, common.Address, *big.Int, *big.Int) error var EmitTicketCreatedEvent func(*vm.EVM, [32]byte) error -var gasUsedSinceStartupCounter = metrics.NewRegisteredCounter("arb/gas_used", nil) // A helper struct that implements String() by marshalling to JSON. // This is useful for logging because it's lazy, so if the log level is too high to print the transaction, @@ -148,6 +145,7 @@ func ProduceBlock( chainContext core.ChainContext, chainConfig *params.ChainConfig, batchFetcher arbostypes.FallibleBatchFetcher, + isMsgForPrefetch bool, ) (*types.Block, types.Receipts, error) { var batchFetchErr error txes, err := ParseL2Transactions(message, chainConfig.ChainID, func(batchNum uint64, batchHash common.Hash) []byte { @@ -173,7 +171,7 @@ func ProduceBlock( hooks := NoopSequencingHooks() return ProduceBlockAdvanced( - message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, + message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, isMsgForPrefetch, ) } @@ -187,6 +185,7 @@ func ProduceBlockAdvanced( chainContext core.ChainContext, chainConfig *params.ChainConfig, sequencingHooks *SequencingHooks, + isMsgForPrefetch bool, ) (*types.Block, types.Receipts, error) { state, err := arbosState.OpenSystemArbosState(statedb, nil, true) @@ -376,7 +375,9 @@ func ProduceBlockAdvanced( if chainConfig.DebugMode() { logLevel = log.Warn } - logLevel("error applying transaction", "tx", printTxAsJson{tx}, "err", err) + if !isMsgForPrefetch { + logLevel("error applying transaction", "tx", printTxAsJson{tx}, "err", err) + } if !hooks.DiscardInvalidTxsEarly { // we'll still deduct a TxGas's worth from the block-local rate limiter even if the tx was invalid blockGasLeft = arbmath.SaturatingUSub(blockGasLeft, params.TxGas) @@ -397,7 +398,7 @@ func ProduceBlockAdvanced( txGasUsed := header.GasUsed - preTxHeaderGasUsed arbosVer := types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion - if arbosVer >= arbostypes.ArbosVersion_FixRedeemGas { + if arbosVer >= params.ArbosVersion_FixRedeemGas { // subtract gas burned for future use for _, scheduledTx := range result.ScheduledTxes { switch inner := scheduledTx.GetInner().(type) { @@ -442,16 +443,14 @@ func ProduceBlockAdvanced( // L2->L1 withdrawals remove eth from the system switch txLog.Topics[0] { case L2ToL1TransactionEventID: - event := &precompilesgen.ArbSysL2ToL1Transaction{} - err := util.ParseL2ToL1TransactionLog(event, txLog) + event, err := util.ParseL2ToL1TransactionLog(txLog) if err != nil { log.Error("Failed to parse L2ToL1Transaction log", "err", err) } else { expectedBalanceDelta.Sub(expectedBalanceDelta, event.Callvalue) } case L2ToL1TxEventID: - event := &precompilesgen.ArbSysL2ToL1Tx{} - err := util.ParseL2ToL1TxLog(event, txLog) + event, err := util.ParseL2ToL1TxLog(txLog) if err != nil { log.Error("Failed to parse L2ToL1Tx log", "err", err) } else { @@ -463,10 +462,6 @@ func ProduceBlockAdvanced( blockGasLeft = arbmath.SaturatingUSub(blockGasLeft, computeUsed) - // Add gas used since startup to prometheus metric. - gasUsed := arbmath.SaturatingUSub(receipt.GasUsed, receipt.GasUsedForL1) - gasUsedSinceStartupCounter.Inc(arbmath.SaturatingCast(gasUsed)) - complete = append(complete, tx) receipts = append(receipts, receipt) diff --git a/arbos/burn/burn.go b/arbos/burn/burn.go index 730fed1a5..7d30ad12e 100644 --- a/arbos/burn/burn.go +++ b/arbos/burn/burn.go @@ -13,6 +13,8 @@ import ( type Burner interface { Burn(amount uint64) error Burned() uint64 + GasLeft() *uint64 // `SystemBurner`s panic (no notion of GasLeft) + BurnOut() error Restrict(err error) HandleError(err error) error ReadOnly() bool @@ -41,6 +43,14 @@ func (burner *SystemBurner) Burned() uint64 { return burner.gasBurnt } +func (burner *SystemBurner) BurnOut() error { + panic("called BurnOut on a system burner") +} + +func (burner *SystemBurner) GasLeft() *uint64 { + panic("called GasLeft on a system burner") +} + func (burner *SystemBurner) Restrict(err error) { if err != nil { glog.Error("Restrict() received an error", "err", err) diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index cd6feb390..9832ac800 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbos @@ -104,8 +104,8 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos if err != nil { log.Warn("L1Pricing PerBatchGas failed", "err", err) } - gasSpent := arbmath.SaturatingAdd(perBatchGas, arbmath.SaturatingCast(batchDataGas)) - weiSpent := arbmath.BigMulByUint(l1BaseFeeWei, arbmath.SaturatingUCast(gasSpent)) + gasSpent := arbmath.SaturatingAdd(perBatchGas, arbmath.SaturatingCast[int64](batchDataGas)) + weiSpent := arbmath.BigMulByUint(l1BaseFeeWei, arbmath.SaturatingUCast[uint64](gasSpent)) err = l1p.UpdateForBatchPosterSpending( evm.StateDB, evm, diff --git a/arbos/l1pricing/l1PricingOldVersions.go b/arbos/l1pricing/l1PricingOldVersions.go index 5c6b6ab7d..821d743e7 100644 --- a/arbos/l1pricing/l1PricingOldVersions.go +++ b/arbos/l1pricing/l1PricingOldVersions.go @@ -4,12 +4,13 @@ package l1pricing import ( + "math" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/offchainlabs/nitro/arbos/util" am "github.com/offchainlabs/nitro/util/arbmath" - "math" - "math/big" ) func (ps *L1PricingState) _preversion10_UpdateForBatchPosterSpending( @@ -105,8 +106,8 @@ func (ps *L1PricingState) _preversion10_UpdateForBatchPosterSpending( // pay rewards, as much as possible paymentForRewards := am.BigMulByUint(am.UintToBig(perUnitReward), unitsAllocated) availableFunds := statedb.GetBalance(L1PricerFundsPoolAddress) - if am.BigLessThan(availableFunds, paymentForRewards) { - paymentForRewards = availableFunds + if am.BigLessThan(availableFunds.ToBig(), paymentForRewards) { + paymentForRewards = availableFunds.ToBig() } fundsDueForRewards = am.BigSub(fundsDueForRewards, paymentForRewards) if err := ps.SetFundsDueForRewards(fundsDueForRewards); err != nil { @@ -130,8 +131,8 @@ func (ps *L1PricingState) _preversion10_UpdateForBatchPosterSpending( return err } balanceToTransfer := balanceDueToPoster - if am.BigLessThan(availableFunds, balanceToTransfer) { - balanceToTransfer = availableFunds + if am.BigLessThan(availableFunds.ToBig(), balanceToTransfer) { + balanceToTransfer = availableFunds.ToBig() } if balanceToTransfer.Sign() > 0 { addrToPay, err := posterState.PayTo() @@ -166,7 +167,7 @@ func (ps *L1PricingState) _preversion10_UpdateForBatchPosterSpending( if err != nil { return err } - surplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) + surplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress).ToBig(), am.BigAdd(totalFundsDue, fundsDueForRewards)) inertia, err := ps.Inertia() if err != nil { @@ -230,7 +231,7 @@ func (ps *L1PricingState) _preVersion2_UpdateForBatchPosterSpending( if err != nil { return err } - oldSurplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) + oldSurplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress).ToBig(), am.BigAdd(totalFundsDue, fundsDueForRewards)) // compute allocation fraction -- will allocate updateTimeDelta/timeDelta fraction of units and funds to this update lastUpdateTime, err := ps.LastUpdateTime() @@ -280,7 +281,7 @@ func (ps *L1PricingState) _preVersion2_UpdateForBatchPosterSpending( // allocate funds to this update collectedSinceUpdate := statedb.GetBalance(L1PricerFundsPoolAddress) - availableFunds := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate, allocationNumerator), allocationDenominator) + availableFunds := am.BigDivByUint(am.BigMulByUint(collectedSinceUpdate.ToBig(), allocationNumerator), allocationDenominator) // pay rewards, as much as possible paymentForRewards := am.BigMulByUint(am.UintToBig(perUnitReward), unitsAllocated) @@ -356,7 +357,7 @@ func (ps *L1PricingState) _preVersion2_UpdateForBatchPosterSpending( if err != nil { return err } - surplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress), am.BigAdd(totalFundsDue, fundsDueForRewards)) + surplus := am.BigSub(statedb.GetBalance(L1PricerFundsPoolAddress).ToBig(), am.BigAdd(totalFundsDue, fundsDueForRewards)) inertia, err := ps.Inertia() if err != nil { diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index f2312c46d..9e00eeb58 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -195,6 +195,23 @@ func (ps *L1PricingState) SetUnitsSinceUpdate(units uint64) error { return ps.unitsSinceUpdate.Set(units) } +func (ps *L1PricingState) GetL1PricingSurplus() (*big.Int, error) { + fundsDueForRefunds, err := ps.BatchPosterTable().TotalFundsDue() + if err != nil { + return nil, err + } + fundsDueForRewards, err := ps.FundsDueForRewards() + if err != nil { + return nil, err + } + haveFunds, err := ps.L1FeesAvailable() + if err != nil { + return nil, err + } + needFunds := arbmath.BigAdd(fundsDueForRefunds, fundsDueForRewards) + return arbmath.BigSub(haveFunds, needFunds), nil +} + func (ps *L1PricingState) LastSurplus() (*big.Int, error) { return ps.lastSurplus.Get() } diff --git a/arbos/l1pricing_test.go b/arbos/l1pricing_test.go index b23c1747a..6e2b1b7ee 100644 --- a/arbos/l1pricing_test.go +++ b/arbos/l1pricing_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" + "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/util" @@ -171,7 +172,7 @@ func _testL1PricingFundsDue(t *testing.T, testParams *l1PricingTest, expectedRes // create some fake collection balanceAdded := big.NewInt(int64(testParams.fundsCollectedPerSecond * 3)) unitsAdded := testParams.unitsPerSecond * 3 - evm.StateDB.AddBalance(l1pricing.L1PricerFundsPoolAddress, balanceAdded) + evm.StateDB.AddBalance(l1pricing.L1PricerFundsPoolAddress, uint256.MustFromBig(balanceAdded)) err = l1p.SetL1FeesAvailable(balanceAdded) Require(t, err) err = l1p.SetUnitsSinceUpdate(unitsAdded) @@ -187,7 +188,7 @@ func _testL1PricingFundsDue(t *testing.T, testParams *l1PricingTest, expectedRes ) Require(t, err) rewardRecipientBalance := evm.StateDB.GetBalance(rewardAddress) - if !arbmath.BigEquals(rewardRecipientBalance, expectedResults.rewardRecipientBalance) { + if !arbmath.BigEquals(rewardRecipientBalance.ToBig(), expectedResults.rewardRecipientBalance) { Fail(t, rewardRecipientBalance, expectedResults.rewardRecipientBalance) } unitsRemaining, err := l1p.UnitsSinceUpdate() @@ -196,16 +197,16 @@ func _testL1PricingFundsDue(t *testing.T, testParams *l1PricingTest, expectedRes Fail(t, unitsRemaining, expectedResults.unitsRemaining) } fundsReceived := evm.StateDB.GetBalance(firstPayTo) - if !arbmath.BigEquals(fundsReceived, expectedResults.fundsReceived) { + if !arbmath.BigEquals(fundsReceived.ToBig(), expectedResults.fundsReceived) { Fail(t, fundsReceived, expectedResults.fundsReceived) } fundsStillHeld := evm.StateDB.GetBalance(l1pricing.L1PricerFundsPoolAddress) - if !arbmath.BigEquals(fundsStillHeld, expectedResults.fundsStillHeld) { + if !arbmath.BigEquals(fundsStillHeld.ToBig(), expectedResults.fundsStillHeld) { Fail(t, fundsStillHeld, expectedResults.fundsStillHeld) } fundsAvail, err := l1p.L1FeesAvailable() Require(t, err) - if fundsStillHeld.Cmp(fundsAvail) != 0 { + if fundsStillHeld.ToBig().Cmp(fundsAvail) != 0 { Fail(t, fundsStillHeld, fundsAvail) } } diff --git a/arbos/l2pricing/model.go b/arbos/l2pricing/model.go index effa6354a..131af2c2c 100644 --- a/arbos/l2pricing/model.go +++ b/arbos/l2pricing/model.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package l2pricing @@ -30,7 +30,7 @@ func (ps *L2PricingState) AddToGasPool(gas int64) error { return err } // pay off some of the backlog with the added gas, stopping at 0 - backlog = arbmath.SaturatingUCast(arbmath.SaturatingSub(int64(backlog), gas)) + backlog = arbmath.SaturatingUCast[uint64](arbmath.SaturatingSub(int64(backlog), gas)) return ps.SetGasBacklog(backlog) } @@ -46,7 +46,7 @@ func (ps *L2PricingState) UpdatePricingModel(l2BaseFee *big.Int, timePassed uint if backlog > tolerance*speedLimit { excess := int64(backlog - tolerance*speedLimit) exponentBips := arbmath.NaturalToBips(excess) / arbmath.Bips(inertia*speedLimit) - baseFee = arbmath.BigMulByBips(minBaseFee, arbmath.ApproxExpBasisPoints(exponentBips)) + baseFee = arbmath.BigMulByBips(minBaseFee, arbmath.ApproxExpBasisPoints(exponentBips, 4)) } _ = ps.SetBaseFeeWei(baseFee) } diff --git a/arbos/programs/api.go b/arbos/programs/api.go new file mode 100644 index 000000000..c8241a72b --- /dev/null +++ b/arbos/programs/api.go @@ -0,0 +1,427 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/util/arbmath" + am "github.com/offchainlabs/nitro/util/arbmath" +) + +type RequestHandler func(req RequestType, input []byte) ([]byte, []byte, uint64) + +type RequestType int +type u256 = uint256.Int + +const ( + GetBytes32 RequestType = iota + SetTrieSlots + GetTransientBytes32 + SetTransientBytes32 + ContractCall + DelegateCall + StaticCall + Create1 + Create2 + EmitLog + AccountBalance + AccountCode + AccountCodeHash + AddPages + CaptureHostIO +) + +type apiStatus uint8 + +const ( + Success apiStatus = iota + Failure + OutOfGas + WriteProtection +) + +func (s apiStatus) to_slice() []byte { + return []byte{uint8(s)} +} + +const EvmApiMethodReqOffset = 0x10000000 + +func newApiClosures( + interpreter *vm.EVMInterpreter, + tracingInfo *util.TracingInfo, + scope *vm.ScopeContext, + memoryModel *MemoryModel, +) RequestHandler { + contract := scope.Contract + actingAddress := contract.Address() // not necessarily WASM + readOnly := interpreter.ReadOnly() + evm := interpreter.Evm() + depth := evm.Depth() + db := evm.StateDB + chainConfig := evm.ChainConfig() + + getBytes32 := func(key common.Hash) (common.Hash, uint64) { + if tracingInfo != nil { + tracingInfo.RecordStorageGet(key) + } + cost := vm.WasmStateLoadCost(db, actingAddress, key) + return db.GetState(actingAddress, key), cost + } + setTrieSlots := func(data []byte, gasLeft *uint64) apiStatus { + for len(data) > 0 { + key := common.BytesToHash(data[:32]) + value := common.BytesToHash(data[32:64]) + data = data[64:] + + if tracingInfo != nil { + tracingInfo.RecordStorageSet(key, value) + } + if readOnly { + return WriteProtection + } + + cost := vm.WasmStateStoreCost(db, actingAddress, key, value) + if cost > *gasLeft { + *gasLeft = 0 + return OutOfGas + } + *gasLeft -= cost + db.SetState(actingAddress, key, value) + } + return Success + } + getTransientBytes32 := func(key common.Hash) common.Hash { + return db.GetTransientState(actingAddress, key) + } + setTransientBytes32 := func(key, value common.Hash) apiStatus { + if readOnly { + return WriteProtection + } + db.SetTransientState(actingAddress, key, value) + return Success + } + doCall := func( + contract common.Address, opcode vm.OpCode, input []byte, gasLeft, gasReq uint64, value *u256, + ) ([]byte, uint64, error) { + // This closure can perform each kind of contract call based on the opcode passed in. + // The implementation for each should match that of the EVM. + // + // Note that while the Yellow Paper is authoritative, the following go-ethereum + // functions provide corresponding implementations in the vm package. + // - operations_acl.go makeCallVariantGasCallEIP2929() + // - gas_table.go gasCall() gasDelegateCall() gasStaticCall() + // - instructions.go opCall() opDelegateCall() opStaticCall() + // + + // read-only calls are not payable (opCall) + if readOnly && value.Sign() != 0 { + return nil, 0, vm.ErrWriteProtection + } + + // computes makeCallVariantGasCallEIP2929 and gasCall/gasDelegateCall/gasStaticCall + baseCost, err := vm.WasmCallCost(db, contract, value, gasLeft) + if err != nil { + return nil, gasLeft, err + } + + // apply the 63/64ths rule + startGas := am.SaturatingUSub(gasLeft, baseCost) * 63 / 64 + gas := am.MinInt(startGas, gasReq) + + // Tracing: emit the call (value transfer is done later in evm.Call) + if tracingInfo != nil { + tracingInfo.Tracer.CaptureState(0, opcode, startGas, baseCost+gas, scope, []byte{}, depth, nil) + } + + // EVM rule: calls that pay get a stipend (opCall) + if value.Sign() != 0 { + gas = am.SaturatingUAdd(gas, params.CallStipend) + } + + var ret []byte + var returnGas uint64 + + switch opcode { + case vm.CALL: + ret, returnGas, err = evm.Call(scope.Contract, contract, input, gas, value) + case vm.DELEGATECALL: + ret, returnGas, err = evm.DelegateCall(scope.Contract, contract, input, gas) + case vm.STATICCALL: + ret, returnGas, err = evm.StaticCall(scope.Contract, contract, input, gas) + default: + log.Crit("unsupported call type", "opcode", opcode) + } + + interpreter.SetReturnData(ret) + cost := am.SaturatingUAdd(baseCost, am.SaturatingUSub(gas, returnGas)) + return ret, cost, err + } + create := func(code []byte, endowment, salt *u256, gas uint64) (common.Address, []byte, uint64, error) { + // This closure can perform both kinds of contract creation based on the salt passed in. + // The implementation for each should match that of the EVM. + // + // Note that while the Yellow Paper is authoritative, the following go-ethereum + // functions provide corresponding implementations in the vm package. + // - instructions.go opCreate() opCreate2() + // - gas_table.go gasCreate() gasCreate2() + // + + opcode := vm.CREATE + if salt != nil { + opcode = vm.CREATE2 + } + zeroAddr := common.Address{} + startGas := gas + + if readOnly { + return zeroAddr, nil, 0, vm.ErrWriteProtection + } + + // pay for static and dynamic costs (gasCreate and gasCreate2) + baseCost := params.CreateGas + if opcode == vm.CREATE2 { + keccakWords := am.WordsForBytes(uint64(len(code))) + keccakCost := am.SaturatingUMul(params.Keccak256WordGas, keccakWords) + baseCost = am.SaturatingUAdd(baseCost, keccakCost) + } + if gas < baseCost { + return zeroAddr, nil, gas, vm.ErrOutOfGas + } + gas -= baseCost + + // apply the 63/64ths rule + one64th := gas / 64 + gas -= one64th + + // Tracing: emit the create + if tracingInfo != nil { + tracingInfo.Tracer.CaptureState(0, opcode, startGas, baseCost+gas, scope, []byte{}, depth, nil) + } + + var res []byte + var addr common.Address // zero on failure + var returnGas uint64 + var suberr error + + if opcode == vm.CREATE { + res, addr, returnGas, suberr = evm.Create(contract, code, gas, endowment) + } else { + res, addr, returnGas, suberr = evm.Create2(contract, code, gas, endowment, salt) + } + if suberr != nil { + addr = zeroAddr + } + if !errors.Is(vm.ErrExecutionReverted, suberr) { + res = nil // returnData is only provided in the revert case (opCreate) + } + interpreter.SetReturnData(res) + cost := arbmath.SaturatingUSub(startGas, returnGas+one64th) // user gets 1/64th back + return addr, res, cost, nil + } + emitLog := func(topics []common.Hash, data []byte) error { + if readOnly { + return vm.ErrWriteProtection + } + event := &types.Log{ + Address: actingAddress, + Topics: topics, + Data: data, + BlockNumber: evm.Context.BlockNumber.Uint64(), + // Geth will set other fields + } + db.AddLog(event) + return nil + } + accountBalance := func(address common.Address) (common.Hash, uint64) { + cost := vm.WasmAccountTouchCost(chainConfig, evm.StateDB, address, false) + balance := evm.StateDB.GetBalance(address) + return balance.Bytes32(), cost + } + accountCode := func(address common.Address, gas uint64) ([]byte, uint64) { + // In the future it'll be possible to know the size of a contract before loading it. + // For now, require the worst case before doing the load. + + cost := vm.WasmAccountTouchCost(chainConfig, evm.StateDB, address, true) + if gas < cost { + return []byte{}, cost + } + return evm.StateDB.GetCode(address), cost + } + accountCodehash := func(address common.Address) (common.Hash, uint64) { + cost := vm.WasmAccountTouchCost(chainConfig, evm.StateDB, address, false) + return evm.StateDB.GetCodeHash(address), cost + } + addPages := func(pages uint16) uint64 { + open, ever := db.AddStylusPages(pages) + return memoryModel.GasCost(pages, open, ever) + } + captureHostio := func(name string, args, outs []byte, startInk, endInk uint64) { + tracingInfo.Tracer.CaptureStylusHostio(name, args, outs, startInk, endInk) + } + + return func(req RequestType, input []byte) ([]byte, []byte, uint64) { + original := input + + crash := func(reason string) { + log.Crit("bad API call", "reason", reason, "request", req, "len", len(original), "remaining", len(input)) + } + takeInput := func(needed int, reason string) []byte { + if len(input) < needed { + crash(reason) + } + data := input[:needed] + input = input[needed:] + return data + } + defer func() { + if len(input) > 0 { + crash("extra input") + } + }() + + takeAddress := func() common.Address { + return common.BytesToAddress(takeInput(20, "expected address")) + } + takeHash := func() common.Hash { + return common.BytesToHash(takeInput(32, "expected hash")) + } + takeU256 := func() *u256 { + return am.BytesToUint256(takeInput(32, "expected big")) + } + takeU64 := func() uint64 { + return am.BytesToUint(takeInput(8, "expected u64")) + } + takeU32 := func() uint32 { + return am.BytesToUint32(takeInput(4, "expected u32")) + } + takeU16 := func() uint16 { + return am.BytesToUint16(takeInput(2, "expected u16")) + } + takeFixed := func(needed int) []byte { + return takeInput(needed, "expected value with known length") + } + takeRest := func() []byte { + data := input + input = []byte{} + return data + } + + switch req { + case GetBytes32: + key := takeHash() + out, cost := getBytes32(key) + return out[:], nil, cost + case SetTrieSlots: + gasLeft := takeU64() + gas := gasLeft + status := setTrieSlots(takeRest(), &gas) + return status.to_slice(), nil, gasLeft - gas + case GetTransientBytes32: + key := takeHash() + out := getTransientBytes32(key) + return out[:], nil, 0 + case SetTransientBytes32: + key := takeHash() + value := takeHash() + status := setTransientBytes32(key, value) + return status.to_slice(), nil, 0 + case ContractCall, DelegateCall, StaticCall: + var opcode vm.OpCode + switch req { + case ContractCall: + opcode = vm.CALL + case DelegateCall: + opcode = vm.DELEGATECALL + case StaticCall: + opcode = vm.STATICCALL + default: + log.Crit("unsupported call type", "opcode", opcode) + } + contract := takeAddress() + value := takeU256() + gasLeft := takeU64() + gasReq := takeU64() + calldata := takeRest() + + ret, cost, err := doCall(contract, opcode, calldata, gasLeft, gasReq, value) + statusByte := byte(0) + if err != nil { + statusByte = 2 // TODO: err value + } + return []byte{statusByte}, ret, cost + case Create1, Create2: + gas := takeU64() + endowment := takeU256() + var salt *u256 + if req == Create2 { + salt = takeU256() + } + code := takeRest() + + address, retVal, cost, err := create(code, endowment, salt, gas) + if err != nil { + res := append([]byte{0}, []byte(err.Error())...) + return res, nil, gas + } + res := append([]byte{1}, address.Bytes()...) + return res, retVal, cost + case EmitLog: + topics := takeU32() + hashes := make([]common.Hash, topics) + for i := uint32(0); i < topics; i++ { + hashes[i] = takeHash() + } + + err := emitLog(hashes, takeRest()) + if err != nil { + return []byte(err.Error()), nil, 0 + } + return []byte{}, nil, 0 + case AccountBalance: + address := takeAddress() + balance, cost := accountBalance(address) + return balance[:], nil, cost + case AccountCode: + address := takeAddress() + gas := takeU64() + code, cost := accountCode(address, gas) + return nil, code, cost + case AccountCodeHash: + address := takeAddress() + codeHash, cost := accountCodehash(address) + return codeHash[:], nil, cost + case AddPages: + pages := takeU16() + cost := addPages(pages) + return []byte{}, nil, cost + case CaptureHostIO: + if tracingInfo == nil { + takeRest() // drop any input + return []byte{}, nil, 0 + } + startInk := takeU64() + endInk := takeU64() + nameLen := takeU16() + argsLen := takeU16() + outsLen := takeU16() + name := string(takeFixed(int(nameLen))) + args := takeFixed(int(argsLen)) + outs := takeFixed(int(outsLen)) + + captureHostio(name, args, outs, startInk, endInk) + return []byte{}, nil, 0 + default: + log.Crit("unsupported call type", "req", req) + return []byte{}, nil, 0 + } + } +} diff --git a/arbos/programs/constant_test.go b/arbos/programs/constant_test.go new file mode 100644 index 000000000..fe29bcf3d --- /dev/null +++ b/arbos/programs/constant_test.go @@ -0,0 +1,13 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import "testing" + +func TestConstants(t *testing.T) { + err := testConstants() + if err != nil { + t.Fatal(err) + } +} diff --git a/arbos/programs/data_pricer.go b/arbos/programs/data_pricer.go new file mode 100644 index 000000000..ed7c98556 --- /dev/null +++ b/arbos/programs/data_pricer.go @@ -0,0 +1,90 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "math/big" + + "github.com/offchainlabs/nitro/arbos/storage" + "github.com/offchainlabs/nitro/util/arbmath" +) + +type DataPricer struct { + backingStorage *storage.Storage + demand storage.StorageBackedUint32 + bytesPerSecond storage.StorageBackedUint32 + lastUpdateTime storage.StorageBackedUint64 + minPrice storage.StorageBackedUint32 + inertia storage.StorageBackedUint32 +} + +const ( + demandOffset uint64 = iota + bytesPerSecondOffset + lastUpdateTimeOffset + minPriceOffset + inertiaOffset +) + +const ArbitrumStartTime = 1421388000 // the day it all began + +const initialDemand = 0 // no demand +const InitialHourlyBytes = 1 * (1 << 40) / (365 * 24) // 1Tb total footprint +const initialBytesPerSecond = InitialHourlyBytes / (60 * 60) // refill each second +const initialLastUpdateTime = ArbitrumStartTime +const initialMinPrice = 82928201 // 5Mb = $1 +const initialInertia = 21360419 // expensive at 1Tb + +func initDataPricer(sto *storage.Storage) { + demand := sto.OpenStorageBackedUint32(demandOffset) + bytesPerSecond := sto.OpenStorageBackedUint32(bytesPerSecondOffset) + lastUpdateTime := sto.OpenStorageBackedUint64(lastUpdateTimeOffset) + minPrice := sto.OpenStorageBackedUint32(minPriceOffset) + inertia := sto.OpenStorageBackedUint32(inertiaOffset) + _ = demand.Set(initialDemand) + _ = bytesPerSecond.Set(initialBytesPerSecond) + _ = lastUpdateTime.Set(initialLastUpdateTime) + _ = minPrice.Set(initialMinPrice) + _ = inertia.Set(initialInertia) +} + +func openDataPricer(sto *storage.Storage) *DataPricer { + return &DataPricer{ + backingStorage: sto, + demand: sto.OpenStorageBackedUint32(demandOffset), + bytesPerSecond: sto.OpenStorageBackedUint32(bytesPerSecondOffset), + lastUpdateTime: sto.OpenStorageBackedUint64(lastUpdateTimeOffset), + minPrice: sto.OpenStorageBackedUint32(minPriceOffset), + inertia: sto.OpenStorageBackedUint32(inertiaOffset), + } +} + +func (p *DataPricer) UpdateModel(tempBytes uint32, time uint64) (*big.Int, error) { + demand, _ := p.demand.Get() + bytesPerSecond, _ := p.bytesPerSecond.Get() + lastUpdateTime, _ := p.lastUpdateTime.Get() + minPrice, _ := p.minPrice.Get() + inertia, err := p.inertia.Get() + if err != nil { + return nil, err + } + + passed := arbmath.SaturatingUUCast[uint32](time - lastUpdateTime) + credit := arbmath.SaturatingUMul(bytesPerSecond, passed) + demand = arbmath.SaturatingUSub(demand, credit) + demand = arbmath.SaturatingUAdd(demand, tempBytes) + + if err := p.demand.Set(demand); err != nil { + return nil, err + } + if err := p.lastUpdateTime.Set(time); err != nil { + return nil, err + } + + exponent := arbmath.OneInBips * arbmath.Bips(demand) / arbmath.Bips(inertia) + multiplier := arbmath.ApproxExpBasisPoints(exponent, 12).Uint64() + costPerByte := arbmath.SaturatingUMul(uint64(minPrice), multiplier) / 10000 + costInWei := arbmath.SaturatingUMul(costPerByte, uint64(tempBytes)) + return arbmath.UintToBig(costInWei), nil +} diff --git a/arbos/programs/memory.go b/arbos/programs/memory.go new file mode 100644 index 000000000..60da3c076 --- /dev/null +++ b/arbos/programs/memory.go @@ -0,0 +1,60 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "math" + + "github.com/offchainlabs/nitro/util/arbmath" +) + +type MemoryModel struct { + freePages uint16 // number of pages the tx gets for free + pageGas uint16 // base gas to charge per wasm page +} + +func NewMemoryModel(freePages uint16, pageGas uint16) *MemoryModel { + return &MemoryModel{ + freePages: freePages, + pageGas: pageGas, + } +} + +// Determines the gas cost of allocating `new` pages given `open` are active and `ever` have ever been. +func (model *MemoryModel) GasCost(new, open, ever uint16) uint64 { + newOpen := arbmath.SaturatingUAdd(open, new) + newEver := arbmath.MaxInt(ever, newOpen) + + // free until expansion beyond the first few + if newEver <= model.freePages { + return 0 + } + subFree := func(pages uint16) uint16 { + return arbmath.SaturatingUSub(pages, model.freePages) + } + + adding := arbmath.SaturatingUSub(subFree(newOpen), subFree(open)) + linear := arbmath.SaturatingUMul(uint64(adding), uint64(model.pageGas)) + expand := model.exp(newEver) - model.exp(ever) + return arbmath.SaturatingUAdd(linear, expand) +} + +func (model *MemoryModel) exp(pages uint16) uint64 { + if int(pages) < len(memoryExponents) { + return uint64(memoryExponents[pages]) + } + return math.MaxUint64 +} + +var memoryExponents = [129]uint32{ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 14, 17, 19, 22, 25, 29, 33, 38, + 43, 50, 57, 65, 75, 85, 98, 112, 128, 147, 168, 193, 221, 253, 289, 331, 379, 434, 497, 569, + 651, 745, 853, 976, 1117, 1279, 1463, 1675, 1917, 2194, 2511, 2874, 3290, 3765, 4309, 4932, + 5645, 6461, 7395, 8464, 9687, 11087, 12689, 14523, 16621, 19024, 21773, 24919, 28521, 32642, + 37359, 42758, 48938, 56010, 64104, 73368, 83971, 96106, 109994, 125890, 144082, 164904, 188735, + 216010, 247226, 282953, 323844, 370643, 424206, 485509, 555672, 635973, 727880, 833067, 953456, + 1091243, 1248941, 1429429, 1636000, 1872423, 2143012, 2452704, 2807151, 3212820, 3677113, + 4208502, 4816684, 5512756, 6309419, 7221210, 8264766, 9459129, 10826093, 12390601, 14181199, + 16230562, 18576084, 21260563, 24332984, 27849408, 31873999, +} diff --git a/arbos/programs/memory_test.go b/arbos/programs/memory_test.go new file mode 100644 index 000000000..322311363 --- /dev/null +++ b/arbos/programs/memory_test.go @@ -0,0 +1,87 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "math" + "testing" + + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func TestTables(t *testing.T) { + model := NewMemoryModel(2, 1000) + base := math.Exp(math.Log(31_874_000) / 128) + for p := uint16(0); p < 129; p++ { + value := uint64(math.Pow(base, float64(p))) + correct := model.exp(p) + + if value != correct { + Fail(t, "wrong value for ", p, value, correct) + } + } + if model.exp(129) != math.MaxUint64 || model.exp(math.MaxUint16) != math.MaxUint64 { + Fail(t) + } +} + +func TestModel(t *testing.T) { + model := NewMemoryModel(2, 1000) + + for jump := uint16(1); jump <= 128; jump++ { + total := uint64(0) + pages := uint16(0) + for pages < 128 { + jump := arbmath.MinInt(jump, 128-pages) + total += model.GasCost(jump, pages, pages) + pages += jump + } + AssertEq(t, total, 31999998) + } + + for jump := uint16(1); jump <= 128; jump++ { + total := uint64(0) + open := uint16(0) + ever := uint16(0) + adds := uint64(0) + for ever < 128 { + jump := arbmath.MinInt(jump, 128-open) + total += model.GasCost(jump, open, ever) + open += jump + ever = arbmath.MaxInt(ever, open) + + if ever > model.freePages { + adds += uint64(arbmath.MinInt(jump, ever-model.freePages)) + } + + // pretend we've deallocated some pages + open -= jump / 2 + } + expected := 31873998 + adds*uint64(model.pageGas) + AssertEq(t, total, expected) + } + + // check saturation + AssertEq(t, math.MaxUint64, model.GasCost(129, 0, 0)) + AssertEq(t, math.MaxUint64, model.GasCost(math.MaxUint16, 0, 0)) + + // check free pages + model = NewMemoryModel(128, 1000) + AssertEq(t, 0, model.GasCost(128, 0, 0)) + AssertEq(t, 0, model.GasCost(128, 0, 128)) + AssertEq(t, math.MaxUint64, model.GasCost(129, 0, 0)) +} + +func Fail(t *testing.T, printables ...interface{}) { + t.Helper() + testhelpers.FailImpl(t, printables...) +} + +func AssertEq[T comparable](t *testing.T, a T, b T) { + t.Helper() + if a != b { + Fail(t, a, "!=", b) + } +} diff --git a/arbos/programs/native.go b/arbos/programs/native.go new file mode 100644 index 000000000..ffb27cb6c --- /dev/null +++ b/arbos/programs/native.go @@ -0,0 +1,342 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + +package programs + +/* +#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm +#include "arbitrator.h" + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef size_t usize; +*/ +import "C" +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbos/burn" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" +) + +type u8 = C.uint8_t +type u16 = C.uint16_t +type u32 = C.uint32_t +type u64 = C.uint64_t +type usize = C.size_t +type cbool = C._Bool +type bytes20 = C.Bytes20 +type bytes32 = C.Bytes32 +type rustBytes = C.RustBytes +type rustSlice = C.RustSlice + +func activateProgram( + db vm.StateDB, + program common.Address, + codehash common.Hash, + wasm []byte, + page_limit uint16, + version uint16, + debug bool, + burner burn.Burner, +) (*activationInfo, error) { + info, asm, module, err := activateProgramInternal(db, program, codehash, wasm, page_limit, version, debug, burner.GasLeft()) + if err != nil { + return nil, err + } + db.ActivateWasm(info.moduleHash, asm, module) + return info, nil +} + +func activateProgramInternal( + db vm.StateDB, + addressForLogging common.Address, + codehash common.Hash, + wasm []byte, + page_limit uint16, + version uint16, + debug bool, + gasLeft *uint64, +) (*activationInfo, []byte, []byte, error) { + output := &rustBytes{} + asmLen := usize(0) + moduleHash := &bytes32{} + stylusData := &C.StylusData{} + codeHash := hashToBytes32(codehash) + + status := userStatus(C.stylus_activate( + goSlice(wasm), + u16(page_limit), + u16(version), + cbool(debug), + output, + &asmLen, + &codeHash, + moduleHash, + stylusData, + (*u64)(gasLeft), + )) + + data, msg, err := status.toResult(output.intoBytes(), debug) + if err != nil { + if debug { + log.Warn("activation failed", "err", err, "msg", msg, "program", addressForLogging) + } + if errors.Is(err, vm.ErrExecutionReverted) { + return nil, nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg) + } + return nil, nil, nil, err + } + + hash := moduleHash.toHash() + split := int(asmLen) + asm := data[:split] + module := data[split:] + + info := &activationInfo{ + moduleHash: hash, + initGas: uint16(stylusData.init_cost), + cachedInitGas: uint16(stylusData.cached_init_cost), + asmEstimate: uint32(stylusData.asm_estimate), + footprint: uint16(stylusData.footprint), + } + return info, asm, module, err +} + +func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) { + localAsm, err := statedb.TryGetActivatedAsm(moduleHash) + if err == nil && len(localAsm) > 0 { + return localAsm, nil + } + + // addressForLogging may be empty or may not correspond to the code, so we need to be careful to use the code passed in separately + wasm, err := getWasmFromContractCode(code) + if err != nil { + log.Error("Failed to reactivate program: getWasm", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err) + return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err) + } + + unlimitedGas := uint64(0xffffffffffff) + // we know program is activated, so it must be in correct version and not use too much memory + info, asm, module, err := activateProgramInternal(statedb, addressForLogging, codeHash, wasm, pagelimit, program.version, debugMode, &unlimitedGas) + if err != nil { + log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err) + return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err) + } + + if info.moduleHash != moduleHash { + log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "got", info.moduleHash) + return nil, fmt.Errorf("failed to reactivate program. address: %v, expected ModuleHash: %v", addressForLogging, moduleHash) + } + + currentHoursSince := hoursSinceArbitrum(time) + if currentHoursSince > program.activatedAt { + // stylus program is active on-chain, and was activated in the past + // so we store it directly to database + batch := statedb.Database().WasmStore().NewBatch() + rawdb.WriteActivation(batch, moduleHash, asm, module) + if err := batch.Write(); err != nil { + log.Error("failed writing re-activation to state", "address", addressForLogging, "err", err) + } + } else { + // program activated recently, possibly in this eth_call + // store it to statedb. It will be stored to database if statedb is commited + statedb.ActivateWasm(info.moduleHash, asm, module) + } + return asm, nil +} + +func callProgram( + address common.Address, + moduleHash common.Hash, + localAsm []byte, + scope *vm.ScopeContext, + interpreter *vm.EVMInterpreter, + tracingInfo *util.TracingInfo, + calldata []byte, + evmData *EvmData, + stylusParams *ProgParams, + memoryModel *MemoryModel, + arbos_tag uint32, +) ([]byte, error) { + db := interpreter.Evm().StateDB + debug := stylusParams.DebugMode + + if len(localAsm) == 0 { + log.Error("missing asm", "program", address, "module", moduleHash) + panic("missing asm") + } + + if db, ok := db.(*state.StateDB); ok { + db.RecordProgram(moduleHash) + } + + evmApi := newApi(interpreter, tracingInfo, scope, memoryModel) + defer evmApi.drop() + + output := &rustBytes{} + status := userStatus(C.stylus_call( + goSlice(localAsm), + goSlice(calldata), + stylusParams.encode(), + evmApi.cNative, + evmData.encode(), + cbool(debug), + output, + (*u64)(&scope.Contract.Gas), + u32(arbos_tag), + )) + + depth := interpreter.Depth() + data, msg, err := status.toResult(output.intoBytes(), debug) + if status == userFailure && debug { + log.Warn("program failure", "err", err, "msg", msg, "program", address, "depth", depth) + } + return data, err +} + +//export handleReqImpl +func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out_response *C.GoSliceData, out_raw_data *C.GoSliceData) { + api := getApi(apiId) + reqData := data.read() + reqType := RequestType(req_type - EvmApiMethodReqOffset) + response, raw_data, cost := api.handler(reqType, reqData) + *costPtr = u64(cost) + api.pinAndRef(response, out_response) + api.pinAndRef(raw_data, out_raw_data) +} + +// Caches a program in Rust. We write a record so that we can undo on revert. +// For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU. +func cacheProgram(db vm.StateDB, module common.Hash, program Program, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { + if runMode == core.MessageCommitMode { + // address is only used for logging + asm, err := getLocalAsm(db, module, common.Address{}, code, codeHash, params.PageLimit, time, debug, program) + if err != nil { + panic("unable to recreate wasm") + } + tag := db.Database().WasmCacheTag() + state.CacheWasmRust(asm, module, program.version, tag, debug) + db.RecordCacheWasm(state.CacheWasm{ModuleHash: module, Version: program.version, Tag: tag, Debug: debug}) + } +} + +// Evicts a program in Rust. We write a record so that we can undo on revert, unless we don't need to (e.g. expired) +// For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU. +func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, runMode core.MessageRunMode, forever bool) { + if runMode == core.MessageCommitMode { + tag := db.Database().WasmCacheTag() + state.EvictWasmRust(module, version, tag, debug) + if !forever { + db.RecordEvictWasm(state.EvictWasm{ModuleHash: module, Version: version, Tag: tag, Debug: debug}) + } + } +} + +func init() { + state.CacheWasmRust = func(asm []byte, moduleHash common.Hash, version uint16, tag uint32, debug bool) { + C.stylus_cache_module(goSlice(asm), hashToBytes32(moduleHash), u16(version), u32(tag), cbool(debug)) + } + state.EvictWasmRust = func(moduleHash common.Hash, version uint16, tag uint32, debug bool) { + C.stylus_evict_module(hashToBytes32(moduleHash), u16(version), u32(tag), cbool(debug)) + } +} + +func ResizeWasmLruCache(size uint32) { + C.stylus_cache_lru_resize(u32(size)) +} + +func (value bytes32) toHash() common.Hash { + hash := common.Hash{} + for index, b := range value.bytes { + hash[index] = byte(b) + } + return hash +} + +func hashToBytes32(hash common.Hash) bytes32 { + value := bytes32{} + for index, b := range hash.Bytes() { + value.bytes[index] = u8(b) + } + return value +} + +func addressToBytes20(addr common.Address) bytes20 { + value := bytes20{} + for index, b := range addr.Bytes() { + value.bytes[index] = u8(b) + } + return value +} + +func (slice *rustSlice) read() []byte { + return arbutil.PointerToSlice((*byte)(slice.ptr), int(slice.len)) +} + +func (vec *rustBytes) read() []byte { + return arbutil.PointerToSlice((*byte)(vec.ptr), int(vec.len)) +} + +func (vec *rustBytes) intoBytes() []byte { + slice := vec.read() + vec.drop() + return slice +} + +func (vec *rustBytes) drop() { + C.stylus_drop_vec(*vec) +} + +func goSlice(slice []byte) C.GoSliceData { + return C.GoSliceData{ + ptr: (*u8)(arbutil.SliceToPointer(slice)), + len: usize(len(slice)), + } +} + +func (params *ProgParams) encode() C.StylusConfig { + pricing := C.PricingParams{ + ink_price: u32(params.InkPrice.ToUint32()), + } + return C.StylusConfig{ + version: u16(params.Version), + max_depth: u32(params.MaxDepth), + pricing: pricing, + } +} + +func (data *EvmData) encode() C.EvmData { + return C.EvmData{ + block_basefee: hashToBytes32(data.blockBasefee), + chainid: u64(data.chainId), + block_coinbase: addressToBytes20(data.blockCoinbase), + block_gas_limit: u64(data.blockGasLimit), + block_number: u64(data.blockNumber), + block_timestamp: u64(data.blockTimestamp), + contract_address: addressToBytes20(data.contractAddress), + module_hash: hashToBytes32(data.moduleHash), + msg_sender: addressToBytes20(data.msgSender), + msg_value: hashToBytes32(data.msgValue), + tx_gas_price: hashToBytes32(data.txGasPrice), + tx_origin: addressToBytes20(data.txOrigin), + reentrant: u32(data.reentrant), + return_data_len: 0, + cached: cbool(data.cached), + tracing: cbool(data.tracing), + } +} diff --git a/arbos/programs/native_api.go b/arbos/programs/native_api.go new file mode 100644 index 000000000..136f74c96 --- /dev/null +++ b/arbos/programs/native_api.go @@ -0,0 +1,95 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + +package programs + +/* +#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm +#include "arbitrator.h" + +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef size_t usize; + +void handleReqImpl(usize api, u32 req_type, RustSlice *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data); +void handleReqWrap(usize api, u32 req_type, RustSlice *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data) { + return handleReqImpl(api, req_type, data, out_cost, out_result, out_raw_data); +} +*/ +import "C" +import ( + "runtime" + "sync" + "sync/atomic" + + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" +) + +var apiObjects sync.Map +var apiIds uintptr // atomic and sequential + +type NativeApi struct { + handler RequestHandler + cNative C.NativeRequestHandler + pinner runtime.Pinner +} + +func newApi( + interpreter *vm.EVMInterpreter, + tracingInfo *util.TracingInfo, + scope *vm.ScopeContext, + memoryModel *MemoryModel, +) NativeApi { + handler := newApiClosures(interpreter, tracingInfo, scope, memoryModel) + apiId := atomic.AddUintptr(&apiIds, 1) + id := usize(apiId) + api := NativeApi{ + handler: handler, + cNative: C.NativeRequestHandler{ + handle_request_fptr: (*[0]byte)(C.handleReqWrap), + id: id, + }, + pinner: runtime.Pinner{}, + } + api.pinner.Pin(&api) + apiObjects.Store(apiId, api) + return api +} + +func getApi(id usize) NativeApi { + any, ok := apiObjects.Load(uintptr(id)) + if !ok { + log.Crit("failed to load stylus Go API", "id", id) + } + api, ok := any.(NativeApi) + if !ok { + log.Crit("wrong type for stylus Go API", "id", id) + } + return api +} + +// Free the API object, and any saved request payloads. +func (api *NativeApi) drop() { + api.pinner.Unpin() + apiObjects.Delete(uintptr(api.cNative.id)) +} + +// Pins a slice until program exit during the call to `drop`. +func (api *NativeApi) pinAndRef(data []byte, goSlice *C.GoSliceData) { + if len(data) > 0 { + dataPointer := arbutil.SliceToPointer(data) + api.pinner.Pin(dataPointer) + goSlice.ptr = (*u8)(dataPointer) + } else { + goSlice.ptr = (*u8)(nil) + } + goSlice.len = usize(len(data)) +} diff --git a/arbos/programs/params.go b/arbos/programs/params.go new file mode 100644 index 000000000..6138e3603 --- /dev/null +++ b/arbos/programs/params.go @@ -0,0 +1,159 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbos/storage" + "github.com/offchainlabs/nitro/arbos/util" + am "github.com/offchainlabs/nitro/util/arbmath" +) + +const MaxWasmSize = 128 * 1024 // max decompressed wasm size (programs are also bounded by compressed size) +const initialStackDepth = 4 * 65536 // 4 page stack. +const InitialFreePages = 2 // 2 pages come free (per tx). +const InitialPageGas = 1000 // linear cost per allocation. +const initialPageRamp = 620674314 // targets 8MB costing 32 million gas, minus the linear term. +const initialPageLimit = 128 // reject wasms with memories larger than 8MB. +const initialInkPrice = 10000 // 1 evm gas buys 10k ink. +const initialMinInitGas = 72 // charge 72 * 128 = 9216 gas. +const initialMinCachedGas = 11 // charge 11 * 32 = 352 gas. +const initialInitCostScalar = 50 // scale costs 1:1 (100%) +const initialCachedCostScalar = 50 // scale costs 1:1 (100%) +const initialExpiryDays = 365 // deactivate after 1 year. +const initialKeepaliveDays = 31 // wait a month before allowing reactivation. +const initialRecentCacheSize = 32 // cache the 32 most recent programs. + +const MinCachedGasUnits = 32 /// 32 gas for each unit +const MinInitGasUnits = 128 // 128 gas for each unit +const CostScalarPercent = 2 // 2% for each unit + +// This struct exists to collect the many Stylus configuration parameters into a single word. +// The items here must only be modified in ArbOwner precompile methods (or in ArbOS upgrades). +type StylusParams struct { + backingStorage *storage.Storage + Version uint16 // must only be changed during ArbOS upgrades + InkPrice uint24 + MaxStackDepth uint32 + FreePages uint16 + PageGas uint16 + PageRamp uint64 + PageLimit uint16 + MinInitGas uint8 // measured in 128-gas increments + MinCachedInitGas uint8 // measured in 32-gas increments + InitCostScalar uint8 // measured in 2% increments + CachedCostScalar uint8 // measured in 2% increments + ExpiryDays uint16 + KeepaliveDays uint16 + BlockCacheSize uint16 +} + +// Provides a view of the Stylus parameters. Call Save() to persist. +// Note: this method never returns nil. +func (p Programs) Params() (*StylusParams, error) { + sto := p.backingStorage.OpenCachedSubStorage(paramsKey) + + // assume reads are warm due to the frequency of access + if err := sto.Burner().Burn(1 * params.WarmStorageReadCostEIP2929); err != nil { + return &StylusParams{}, err + } + + // paid for the reads above + next := uint64(0) + data := []byte{} + take := func(count int) []byte { + if len(data) < count { + word := sto.GetFree(util.UintToHash(next)) + data = word[:] + next += 1 + } + value := data[:count] + data = data[count:] + return value + } + + // order matters! + return &StylusParams{ + backingStorage: sto, + Version: am.BytesToUint16(take(2)), + InkPrice: am.BytesToUint24(take(3)), + MaxStackDepth: am.BytesToUint32(take(4)), + FreePages: am.BytesToUint16(take(2)), + PageGas: am.BytesToUint16(take(2)), + PageRamp: initialPageRamp, + PageLimit: am.BytesToUint16(take(2)), + MinInitGas: am.BytesToUint8(take(1)), + MinCachedInitGas: am.BytesToUint8(take(1)), + InitCostScalar: am.BytesToUint8(take(1)), + CachedCostScalar: am.BytesToUint8(take(1)), + ExpiryDays: am.BytesToUint16(take(2)), + KeepaliveDays: am.BytesToUint16(take(2)), + BlockCacheSize: am.BytesToUint16(take(2)), + }, nil +} + +// Writes the params to permanent storage. +func (p *StylusParams) Save() error { + if p.backingStorage == nil { + log.Error("tried to Save invalid StylusParams") + return errors.New("invalid StylusParams") + } + + // order matters! + data := am.ConcatByteSlices( + am.Uint16ToBytes(p.Version), + am.Uint24ToBytes(p.InkPrice), + am.Uint32ToBytes(p.MaxStackDepth), + am.Uint16ToBytes(p.FreePages), + am.Uint16ToBytes(p.PageGas), + am.Uint16ToBytes(p.PageLimit), + am.Uint8ToBytes(p.MinInitGas), + am.Uint8ToBytes(p.MinCachedInitGas), + am.Uint8ToBytes(p.InitCostScalar), + am.Uint8ToBytes(p.CachedCostScalar), + am.Uint16ToBytes(p.ExpiryDays), + am.Uint16ToBytes(p.KeepaliveDays), + am.Uint16ToBytes(p.BlockCacheSize), + ) + + slot := uint64(0) + for len(data) != 0 { + next := am.MinInt(32, len(data)) + info := data[:next] + data = data[next:] + + word := common.Hash{} + copy(word[:], info) // right-pad with zeros + if err := p.backingStorage.SetByUint64(slot, word); err != nil { + return err + } + slot += 1 + } + return nil +} + +func initStylusParams(sto *storage.Storage) { + params := &StylusParams{ + backingStorage: sto, + Version: 1, + InkPrice: initialInkPrice, + MaxStackDepth: initialStackDepth, + FreePages: InitialFreePages, + PageGas: InitialPageGas, + PageRamp: initialPageRamp, + PageLimit: initialPageLimit, + MinInitGas: initialMinInitGas, + MinCachedInitGas: initialMinCachedGas, + InitCostScalar: initialInitCostScalar, + CachedCostScalar: initialCachedCostScalar, + ExpiryDays: initialExpiryDays, + KeepaliveDays: initialKeepaliveDays, + BlockCacheSize: initialRecentCacheSize, + } + _ = params.Save() +} diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go new file mode 100644 index 000000000..bfe48ec87 --- /dev/null +++ b/arbos/programs/programs.go @@ -0,0 +1,553 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package programs + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbos/addressSet" + "github.com/offchainlabs/nitro/arbos/storage" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" + am "github.com/offchainlabs/nitro/util/arbmath" +) + +type Programs struct { + backingStorage *storage.Storage + programs *storage.Storage + moduleHashes *storage.Storage + dataPricer *DataPricer + cacheManagers *addressSet.AddressSet +} + +type Program struct { + version uint16 + initCost uint16 + cachedCost uint16 + footprint uint16 + asmEstimateKb uint24 // Predicted size of the asm + activatedAt uint24 // Hours since Arbitrum began + ageSeconds uint64 // Not stored in state + cached bool +} + +type uint24 = am.Uint24 + +var paramsKey = []byte{0} +var programDataKey = []byte{1} +var moduleHashesKey = []byte{2} +var dataPricerKey = []byte{3} +var cacheManagersKey = []byte{4} + +var ErrProgramActivation = errors.New("program activation failed") + +var ProgramNotWasmError func() error +var ProgramNotActivatedError func() error +var ProgramNeedsUpgradeError func(version, stylusVersion uint16) error +var ProgramExpiredError func(age uint64) error +var ProgramUpToDateError func() error +var ProgramKeepaliveTooSoon func(age uint64) error + +func Initialize(sto *storage.Storage) { + initStylusParams(sto.OpenSubStorage(paramsKey)) + initDataPricer(sto.OpenSubStorage(dataPricerKey)) + _ = addressSet.Initialize(sto.OpenCachedSubStorage(cacheManagersKey)) +} + +func Open(sto *storage.Storage) *Programs { + return &Programs{ + backingStorage: sto, + programs: sto.OpenSubStorage(programDataKey), + moduleHashes: sto.OpenSubStorage(moduleHashesKey), + dataPricer: openDataPricer(sto.OpenCachedSubStorage(dataPricerKey)), + cacheManagers: addressSet.OpenAddressSet(sto.OpenCachedSubStorage(cacheManagersKey)), + } +} + +func (p Programs) DataPricer() *DataPricer { + return p.dataPricer +} + +func (p Programs) CacheManagers() *addressSet.AddressSet { + return p.cacheManagers +} + +func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode core.MessageRunMode, debugMode bool) ( + uint16, common.Hash, common.Hash, *big.Int, bool, error, +) { + statedb := evm.StateDB + codeHash := statedb.GetCodeHash(address) + burner := p.programs.Burner() + time := evm.Context.Time + + if statedb.HasSelfDestructed(address) { + return 0, codeHash, common.Hash{}, nil, false, errors.New("self destructed") + } + + params, err := p.Params() + if err != nil { + return 0, codeHash, common.Hash{}, nil, false, err + } + + stylusVersion := params.Version + currentVersion, expired, cached, err := p.programExists(codeHash, time, params) + if err != nil { + return 0, codeHash, common.Hash{}, nil, false, err + } + if currentVersion == stylusVersion && !expired { + // already activated and up to date + return 0, codeHash, common.Hash{}, nil, false, ProgramUpToDateError() + } + wasm, err := getWasm(statedb, address) + if err != nil { + return 0, codeHash, common.Hash{}, nil, false, err + } + + // require the program's footprint not exceed the remaining memory budget + pageLimit := am.SaturatingUSub(params.PageLimit, statedb.GetStylusPagesOpen()) + + info, err := activateProgram(statedb, address, codeHash, wasm, pageLimit, stylusVersion, debugMode, burner) + if err != nil { + return 0, codeHash, common.Hash{}, nil, true, err + } + + // remove prev asm + if cached { + oldModuleHash, err := p.moduleHashes.Get(codeHash) + if err != nil { + return 0, codeHash, common.Hash{}, nil, true, err + } + evictProgram(statedb, oldModuleHash, currentVersion, debugMode, runMode, expired) + } + if err := p.moduleHashes.Set(codeHash, info.moduleHash); err != nil { + return 0, codeHash, common.Hash{}, nil, true, err + } + + estimateKb, err := am.IntToUint24(am.DivCeil(info.asmEstimate, 1024)) // stored in kilobytes + if err != nil { + return 0, codeHash, common.Hash{}, nil, true, err + } + + dataFee, err := p.dataPricer.UpdateModel(info.asmEstimate, time) + if err != nil { + return 0, codeHash, common.Hash{}, nil, true, err + } + + programData := Program{ + version: stylusVersion, + initCost: info.initGas, + cachedCost: info.cachedInitGas, + footprint: info.footprint, + asmEstimateKb: estimateKb, + activatedAt: hoursSinceArbitrum(time), + cached: cached, + } + // replace the cached asm + if cached { + code := statedb.GetCode(address) + cacheProgram(statedb, info.moduleHash, programData, code, codeHash, params, debugMode, time, runMode) + } + + return stylusVersion, codeHash, info.moduleHash, dataFee, false, p.setProgram(codeHash, programData) +} + +func (p Programs) CallProgram( + scope *vm.ScopeContext, + statedb vm.StateDB, + interpreter *vm.EVMInterpreter, + tracingInfo *util.TracingInfo, + calldata []byte, + reentrant bool, + runmode core.MessageRunMode, +) ([]byte, error) { + evm := interpreter.Evm() + contract := scope.Contract + codeHash := contract.CodeHash + debugMode := evm.ChainConfig().DebugMode() + + params, err := p.Params() + if err != nil { + return nil, err + } + + program, err := p.getActiveProgram(codeHash, evm.Context.Time, params) + if err != nil { + return nil, err + } + moduleHash, err := p.moduleHashes.Get(codeHash) + if err != nil { + return nil, err + } + goParams := p.progParams(program.version, debugMode, params) + l1BlockNumber, err := evm.ProcessingHook.L1BlockNumber(evm.Context) + if err != nil { + return nil, err + } + + // pay for memory init + open, ever := statedb.GetStylusPages() + model := NewMemoryModel(params.FreePages, params.PageGas) + callCost := model.GasCost(program.footprint, open, ever) + + // pay for program init + cached := program.cached || statedb.GetRecentWasms().Insert(codeHash, params.BlockCacheSize) + if cached { + callCost = am.SaturatingUAdd(callCost, program.cachedGas(params)) + } else { + callCost = am.SaturatingUAdd(callCost, program.initGas(params)) + } + if err := contract.BurnGas(callCost); err != nil { + return nil, err + } + statedb.AddStylusPages(program.footprint) + defer statedb.SetStylusPagesOpen(open) + + localAsm, err := getLocalAsm(statedb, moduleHash, contract.Address(), contract.Code, contract.CodeHash, params.PageLimit, evm.Context.Time, debugMode, program) + if err != nil { + log.Crit("failed to get local wasm for activated program", "program", contract.Address()) + return nil, err + } + + evmData := &EvmData{ + blockBasefee: common.BigToHash(evm.Context.BaseFee), + chainId: evm.ChainConfig().ChainID.Uint64(), + blockCoinbase: evm.Context.Coinbase, + blockGasLimit: evm.Context.GasLimit, + blockNumber: l1BlockNumber, + blockTimestamp: evm.Context.Time, + contractAddress: scope.Contract.Address(), + moduleHash: moduleHash, + msgSender: scope.Contract.Caller(), + msgValue: scope.Contract.Value().Bytes32(), + txGasPrice: common.BigToHash(evm.TxContext.GasPrice), + txOrigin: evm.TxContext.Origin, + reentrant: am.BoolToUint32(reentrant), + cached: program.cached, + tracing: tracingInfo != nil, + } + + address := contract.Address() + if contract.CodeAddr != nil { + address = *contract.CodeAddr + } + var arbos_tag uint32 + if runmode == core.MessageCommitMode { + arbos_tag = statedb.Database().WasmCacheTag() + } + return callProgram(address, moduleHash, localAsm, scope, interpreter, tracingInfo, calldata, evmData, goParams, model, arbos_tag) +} + +func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { + prefixedWasm := statedb.GetCode(program) + return getWasmFromContractCode(prefixedWasm) +} + +func getWasmFromContractCode(prefixedWasm []byte) ([]byte, error) { + if prefixedWasm == nil { + return nil, ProgramNotWasmError() + } + wasm, dictByte, err := state.StripStylusPrefix(prefixedWasm) + if err != nil { + return nil, err + } + + var dict arbcompress.Dictionary + switch dictByte { + case 0: + dict = arbcompress.EmptyDictionary + case 1: + dict = arbcompress.StylusProgramDictionary + default: + return nil, fmt.Errorf("unsupported dictionary %v", dictByte) + } + return arbcompress.DecompressWithDictionary(wasm, MaxWasmSize, dict) +} + +// Gets a program entry, which may be expired or not yet activated. +func (p Programs) getProgram(codeHash common.Hash, time uint64) (Program, error) { + data, err := p.programs.Get(codeHash) + program := Program{ + version: am.BytesToUint16(data[:2]), + initCost: am.BytesToUint16(data[2:4]), + cachedCost: am.BytesToUint16(data[4:6]), + footprint: am.BytesToUint16(data[6:8]), + activatedAt: am.BytesToUint24(data[8:11]), + asmEstimateKb: am.BytesToUint24(data[11:14]), + cached: am.BytesToBool(data[14:15]), + } + program.ageSeconds = hoursToAge(time, program.activatedAt) + return program, err +} + +// Gets a program entry. Errors if not active. +func (p Programs) getActiveProgram(codeHash common.Hash, time uint64, params *StylusParams) (Program, error) { + program, err := p.getProgram(codeHash, time) + if err != nil { + return program, err + } + if program.version == 0 { + return program, ProgramNotActivatedError() + } + + // check that the program is up to date + stylusVersion := params.Version + if program.version != stylusVersion { + return program, ProgramNeedsUpgradeError(program.version, stylusVersion) + } + + // ensure the program hasn't expired + if program.ageSeconds > am.DaysToSeconds(params.ExpiryDays) { + return program, ProgramExpiredError(program.ageSeconds) + } + return program, nil +} + +func (p Programs) setProgram(codehash common.Hash, program Program) error { + data := common.Hash{} + copy(data[0:], am.Uint16ToBytes(program.version)) + copy(data[2:], am.Uint16ToBytes(program.initCost)) + copy(data[4:], am.Uint16ToBytes(program.cachedCost)) + copy(data[6:], am.Uint16ToBytes(program.footprint)) + copy(data[8:], am.Uint24ToBytes(program.activatedAt)) + copy(data[11:], am.Uint24ToBytes(program.asmEstimateKb)) + copy(data[14:], am.BoolToBytes(program.cached)) + return p.programs.Set(codehash, data) +} + +func (p Programs) programExists(codeHash common.Hash, time uint64, params *StylusParams) (uint16, bool, bool, error) { + program, err := p.getProgram(codeHash, time) + if err != nil { + return 0, false, false, err + } + activatedAt := program.activatedAt + expired := activatedAt == 0 || hoursToAge(time, activatedAt) > am.DaysToSeconds(params.ExpiryDays) + return program.version, expired, program.cached, err +} + +func (p Programs) ProgramKeepalive(codeHash common.Hash, time uint64, params *StylusParams) (*big.Int, error) { + program, err := p.getActiveProgram(codeHash, time, params) + if err != nil { + return nil, err + } + if program.ageSeconds < am.DaysToSeconds(params.KeepaliveDays) { + return nil, ProgramKeepaliveTooSoon(program.ageSeconds) + } + + stylusVersion := params.Version + if program.version != stylusVersion { + return nil, ProgramNeedsUpgradeError(program.version, stylusVersion) + } + + dataFee, err := p.dataPricer.UpdateModel(program.asmSize(), time) + if err != nil { + return nil, err + } + program.activatedAt = hoursSinceArbitrum(time) + return dataFee, p.setProgram(codeHash, program) +} + +// Gets whether a program is cached. Note that the program may be expired. +func (p Programs) ProgramCached(codeHash common.Hash) (bool, error) { + data, err := p.programs.Get(codeHash) + return am.BytesToBool(data[14:15]), err +} + +// Sets whether a program is cached. Errors if trying to cache an expired program. +func (p Programs) SetProgramCached( + emitEvent func() error, + db vm.StateDB, + codeHash common.Hash, + cache bool, + time uint64, + params *StylusParams, + runMode core.MessageRunMode, + debug bool, +) error { + program, err := p.getProgram(codeHash, time) + if err != nil { + return err + } + expired := program.ageSeconds > am.DaysToSeconds(params.ExpiryDays) + + if program.version == 0 && cache { + return ProgramNeedsUpgradeError(0, params.Version) + } + if expired && cache { + return ProgramExpiredError(program.ageSeconds) + } + if program.cached == cache { + return nil + } + if err := emitEvent(); err != nil { + return err + } + + // pay to cache the program, or to re-cache in case of upcoming revert + if err := p.programs.Burner().Burn(uint64(program.initCost)); err != nil { + return err + } + moduleHash, err := p.moduleHashes.Get(codeHash) + if err != nil { + return err + } + if cache { + // Not passing in an address is supported pre-Verkle, as in Blockchain's ContractCodeWithPrefix method. + code, err := db.Database().ContractCode(common.Address{}, codeHash) + if err != nil { + return err + } + cacheProgram(db, moduleHash, program, code, codeHash, params, debug, time, runMode) + } else { + evictProgram(db, moduleHash, program.version, debug, runMode, expired) + } + program.cached = cache + return p.setProgram(codeHash, program) +} + +func (p Programs) CodehashVersion(codeHash common.Hash, time uint64, params *StylusParams) (uint16, error) { + program, err := p.getActiveProgram(codeHash, time, params) + if err != nil { + return 0, err + } + return program.version, nil +} + +// Gets the number of seconds left until expiration. Errors if it's already happened. +func (p Programs) ProgramTimeLeft(codeHash common.Hash, time uint64, params *StylusParams) (uint64, error) { + program, err := p.getActiveProgram(codeHash, time, params) + if err != nil { + return 0, err + } + age := hoursToAge(time, program.activatedAt) + expirySeconds := am.DaysToSeconds(params.ExpiryDays) + if age > expirySeconds { + return 0, ProgramExpiredError(age) + } + return am.SaturatingUSub(expirySeconds, age), nil +} + +func (p Programs) ProgramInitGas(codeHash common.Hash, time uint64, params *StylusParams) (uint64, uint64, error) { + program, err := p.getActiveProgram(codeHash, time, params) + return program.initGas(params), program.cachedGas(params), err +} + +func (p Programs) ProgramMemoryFootprint(codeHash common.Hash, time uint64, params *StylusParams) (uint16, error) { + program, err := p.getActiveProgram(codeHash, time, params) + return program.footprint, err +} + +func (p Programs) ProgramAsmSize(codeHash common.Hash, time uint64, params *StylusParams) (uint32, error) { + program, err := p.getActiveProgram(codeHash, time, params) + if err != nil { + return 0, err + } + return program.asmSize(), nil +} + +func (p Program) asmSize() uint32 { + return am.SaturatingUMul(p.asmEstimateKb.ToUint32(), 1024) +} + +func (p Program) initGas(params *StylusParams) uint64 { + base := uint64(params.MinInitGas) * MinInitGasUnits + dyno := am.SaturatingUMul(uint64(p.initCost), uint64(params.InitCostScalar)*CostScalarPercent) + return am.SaturatingUAdd(base, am.DivCeil(dyno, 100)) +} + +func (p Program) cachedGas(params *StylusParams) uint64 { + base := uint64(params.MinCachedInitGas) * MinCachedGasUnits + dyno := am.SaturatingUMul(uint64(p.cachedCost), uint64(params.CachedCostScalar)*CostScalarPercent) + return am.SaturatingUAdd(base, am.DivCeil(dyno, 100)) +} + +type ProgParams struct { + Version uint16 + MaxDepth uint32 + InkPrice uint24 + DebugMode bool +} + +func (p Programs) progParams(version uint16, debug bool, params *StylusParams) *ProgParams { + return &ProgParams{ + Version: version, + MaxDepth: params.MaxStackDepth, + InkPrice: params.InkPrice, + DebugMode: debug, + } +} + +type EvmData struct { + blockBasefee common.Hash + chainId uint64 + blockCoinbase common.Address + blockGasLimit uint64 + blockNumber uint64 + blockTimestamp uint64 + contractAddress common.Address + moduleHash common.Hash + msgSender common.Address + msgValue common.Hash + txGasPrice common.Hash + txOrigin common.Address + reentrant uint32 + cached bool + tracing bool +} + +type activationInfo struct { + moduleHash common.Hash + initGas uint16 + cachedInitGas uint16 + asmEstimate uint32 + footprint uint16 +} + +type userStatus uint8 + +const ( + userSuccess userStatus = iota + userRevert + userFailure + userOutOfInk + userOutOfStack +) + +func (status userStatus) toResult(data []byte, debug bool) ([]byte, string, error) { + msg := arbutil.ToStringOrHex(data) + switch status { + case userSuccess: + return data, "", nil + case userRevert: + return data, msg, vm.ErrExecutionReverted + case userFailure: + return nil, msg, vm.ErrExecutionReverted + case userOutOfInk: + return nil, "", vm.ErrOutOfGas + case userOutOfStack: + return nil, "", vm.ErrDepth + default: + log.Error("program errored with unknown status", "status", status, "data", msg) + return nil, msg, vm.ErrExecutionReverted + } +} + +// Hours since Arbitrum began, rounded down. +func hoursSinceArbitrum(time uint64) uint24 { + return am.SaturatingUUCast[uint24]((am.SaturatingUSub(time, ArbitrumStartTime)) / 3600) +} + +// Computes program age in seconds from the hours passed since Arbitrum began. +func hoursToAge(time uint64, hours uint24) uint64 { + seconds := am.SaturatingUMul(uint64(hours), 3600) + activatedAt := am.SaturatingUAdd(ArbitrumStartTime, seconds) + return am.SaturatingUSub(time, activatedAt) +} diff --git a/arbos/programs/testconstants.go b/arbos/programs/testconstants.go new file mode 100644 index 000000000..1ab0e6e93 --- /dev/null +++ b/arbos/programs/testconstants.go @@ -0,0 +1,101 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + +package programs + +// This file exists because cgo isn't allowed in tests + +/* +#cgo CFLAGS: -g -Wall -I../../target/include/ +#include "arbitrator.h" +*/ +import "C" +import "fmt" + +func testConstants() error { + + // this closure exists to avoid polluting the package namespace + index := 1 + errIfNotEq := func(a RequestType, b uint32) error { + if uint32(a) != b { + return fmt.Errorf("constant test %d failed! %d != %d", index, a, b) + } + index += 1 + return nil + } + + if err := errIfNotEq(GetBytes32, C.EvmApiMethod_GetBytes32); err != nil { + return err + } + if err := errIfNotEq(SetTrieSlots, C.EvmApiMethod_SetTrieSlots); err != nil { + return err + } + if err := errIfNotEq(GetTransientBytes32, C.EvmApiMethod_GetTransientBytes32); err != nil { + return err + } + if err := errIfNotEq(SetTransientBytes32, C.EvmApiMethod_SetTransientBytes32); err != nil { + return err + } + if err := errIfNotEq(ContractCall, C.EvmApiMethod_ContractCall); err != nil { + return err + } + if err := errIfNotEq(DelegateCall, C.EvmApiMethod_DelegateCall); err != nil { + return err + } + if err := errIfNotEq(StaticCall, C.EvmApiMethod_StaticCall); err != nil { + return err + } + if err := errIfNotEq(Create1, C.EvmApiMethod_Create1); err != nil { + return err + } + if err := errIfNotEq(Create2, C.EvmApiMethod_Create2); err != nil { + return err + } + if err := errIfNotEq(EmitLog, C.EvmApiMethod_EmitLog); err != nil { + return err + } + if err := errIfNotEq(AccountBalance, C.EvmApiMethod_AccountBalance); err != nil { + return err + } + if err := errIfNotEq(AccountCode, C.EvmApiMethod_AccountCode); err != nil { + return err + } + if err := errIfNotEq(AccountCodeHash, C.EvmApiMethod_AccountCodeHash); err != nil { + return err + } + if err := errIfNotEq(AddPages, C.EvmApiMethod_AddPages); err != nil { + return err + } + if err := errIfNotEq(CaptureHostIO, C.EvmApiMethod_CaptureHostIO); err != nil { + return err + } + if err := errIfNotEq(EvmApiMethodReqOffset, C.EVM_API_METHOD_REQ_OFFSET); err != nil { + return err + } + + index = 0 + assertEq := func(a apiStatus, b uint32) error { + if uint32(a) != b { + return fmt.Errorf("constant test %d failed! %d != %d", index, a, b) + } + index += 1 + return nil + } + + if err := assertEq(Success, C.EvmApiStatus_Success); err != nil { + return err + } + if err := assertEq(Failure, C.EvmApiStatus_Failure); err != nil { + return err + } + if err := assertEq(OutOfGas, C.EvmApiStatus_OutOfGas); err != nil { + return err + } + if err := assertEq(WriteProtection, C.EvmApiStatus_WriteProtection); err != nil { + return err + } + return nil +} diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go new file mode 100644 index 000000000..0301a7e84 --- /dev/null +++ b/arbos/programs/wasm.go @@ -0,0 +1,200 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build wasm +// +build wasm + +package programs + +import ( + "errors" + "unsafe" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbos/burn" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/arbmath" +) + +type addr = common.Address +type hash = common.Hash + +// rust types +type u8 = uint8 +type u16 = uint16 +type u32 = uint32 +type u64 = uint64 +type usize = uintptr + +// opaque types +type rustVec byte +type rustConfig byte +type rustModule byte +type rustEvmData byte + +//go:wasmimport programs activate +func programActivate( + wasm_ptr unsafe.Pointer, + wasm_size uint32, + pages_ptr unsafe.Pointer, + asm_estimation_ptr unsafe.Pointer, + init_gas_ptr unsafe.Pointer, + cached_init_gas_ptr unsafe.Pointer, + version uint32, + debug uint32, + codehash unsafe.Pointer, + module_hash_ptr unsafe.Pointer, + gas_ptr unsafe.Pointer, + err_buf unsafe.Pointer, + err_buf_len uint32, +) uint32 + +func activateProgram( + db vm.StateDB, + program addr, + codehash common.Hash, + wasm []byte, + pageLimit u16, + version u16, + debug bool, + burner burn.Burner, +) (*activationInfo, error) { + errBuf := make([]byte, 1024) + debugMode := arbmath.BoolToUint32(debug) + moduleHash := common.Hash{} + gasPtr := burner.GasLeft() + asmEstimate := uint32(0) + initGas := uint16(0) + cachedInitGas := uint16(0) + + footprint := uint16(pageLimit) + errLen := programActivate( + arbutil.SliceToUnsafePointer(wasm), + uint32(len(wasm)), + unsafe.Pointer(&footprint), + unsafe.Pointer(&asmEstimate), + unsafe.Pointer(&initGas), + unsafe.Pointer(&cachedInitGas), + uint32(version), + debugMode, + arbutil.SliceToUnsafePointer(codehash[:]), + arbutil.SliceToUnsafePointer(moduleHash[:]), + unsafe.Pointer(gasPtr), + arbutil.SliceToUnsafePointer(errBuf), + uint32(len(errBuf)), + ) + if errLen != 0 { + err := errors.New(string(errBuf[:errLen])) + return nil, err + } + return &activationInfo{moduleHash, initGas, cachedInitGas, asmEstimate, footprint}, nil +} + +// stub any non-consensus, Rust-side caching updates +func cacheProgram(db vm.StateDB, module common.Hash, program Program, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { +} +func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, mode core.MessageRunMode, forever bool) { +} + +//go:wasmimport programs new_program +func newProgram( + hashPtr unsafe.Pointer, + callDataPtr unsafe.Pointer, + callDataSize uint32, + configHandler stylusConfigHandler, + evmHandler evmDataHandler, + gas uint64, +) uint32 + +//go:wasmimport programs pop +func popProgram() + +//go:wasmimport programs set_response +func setResponse(id uint32, gas uint64, result unsafe.Pointer, result_len uint32, raw_data unsafe.Pointer, raw_data_len uint32) + +//go:wasmimport programs get_request +func getRequest(id uint32, reqLen unsafe.Pointer) uint32 + +//go:wasmimport programs get_request_data +func getRequestData(id uint32, dataPtr unsafe.Pointer) + +//go:wasmimport programs start_program +func startProgram(module uint32) uint32 + +//go:wasmimport programs send_response +func sendResponse(req_id uint32) uint32 + +func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) { + return nil, nil +} + +func callProgram( + address common.Address, + moduleHash common.Hash, + _localAsm []byte, + scope *vm.ScopeContext, + interpreter *vm.EVMInterpreter, + tracingInfo *util.TracingInfo, + calldata []byte, + evmData *EvmData, + params *ProgParams, + memoryModel *MemoryModel, + _arbos_tag uint32, +) ([]byte, error) { + reqHandler := newApiClosures(interpreter, tracingInfo, scope, memoryModel) + gasLeft, retData, err := CallProgramLoop(moduleHash, calldata, scope.Contract.Gas, evmData, params, reqHandler) + scope.Contract.Gas = gasLeft + return retData, err +} + +func CallProgramLoop( + moduleHash common.Hash, + calldata []byte, + gas uint64, + evmData *EvmData, + params *ProgParams, + reqHandler RequestHandler) (uint64, []byte, error) { + configHandler := params.createHandler() + dataHandler := evmData.createHandler() + debug := params.DebugMode + + module := newProgram( + unsafe.Pointer(&moduleHash[0]), + arbutil.SliceToUnsafePointer(calldata), + uint32(len(calldata)), + configHandler, + dataHandler, + gas, + ) + reqId := startProgram(module) + for { + var reqLen uint32 + reqTypeId := getRequest(reqId, unsafe.Pointer(&reqLen)) + reqData := make([]byte, reqLen) + getRequestData(reqId, arbutil.SliceToUnsafePointer(reqData)) + if reqTypeId < EvmApiMethodReqOffset { + popProgram() + status := userStatus(reqTypeId) + gasLeft := arbmath.BytesToUint(reqData[:8]) + data, msg, err := status.toResult(reqData[8:], debug) + if status == userFailure && debug { + log.Warn("program failure", "err", err, "msg", msg, "moduleHash", moduleHash) + } + return gasLeft, data, err + } + + reqType := RequestType(reqTypeId - EvmApiMethodReqOffset) + result, rawData, cost := reqHandler(reqType, reqData) + setResponse( + reqId, + cost, + arbutil.SliceToUnsafePointer(result), uint32(len(result)), + arbutil.SliceToUnsafePointer(rawData), uint32(len(rawData)), + ) + reqId = sendResponse(reqId) + } +} diff --git a/arbos/programs/wasm_api.go b/arbos/programs/wasm_api.go new file mode 100644 index 000000000..d7bac056c --- /dev/null +++ b/arbos/programs/wasm_api.go @@ -0,0 +1,63 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build wasm +// +build wasm + +package programs + +import ( + "unsafe" + + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/arbmath" +) + +type stylusConfigHandler uint64 + +//go:wasmimport programs create_stylus_config +func createStylusConfig(version uint32, max_depth uint32, ink_price uint32, debug uint32) stylusConfigHandler + +type evmDataHandler uint64 + +//go:wasmimport programs create_evm_data +func createEvmData( + blockBaseFee unsafe.Pointer, + chainid uint64, + blockCoinbase unsafe.Pointer, + gasLimit uint64, + blockNumber uint64, + blockTimestamp uint64, + contractAddress unsafe.Pointer, + moduleHash unsafe.Pointer, + msgSender unsafe.Pointer, + msgValue unsafe.Pointer, + txGasPrice unsafe.Pointer, + txOrigin unsafe.Pointer, + cached uint32, + reentrant uint32, +) evmDataHandler + +func (params *ProgParams) createHandler() stylusConfigHandler { + debug := arbmath.BoolToUint32(params.DebugMode) + return createStylusConfig(uint32(params.Version), params.MaxDepth, params.InkPrice.ToUint32(), debug) +} + +func (data *EvmData) createHandler() evmDataHandler { + return createEvmData( + arbutil.SliceToUnsafePointer(data.blockBasefee[:]), + data.chainId, + arbutil.SliceToUnsafePointer(data.blockCoinbase[:]), + data.blockGasLimit, + data.blockNumber, + data.blockTimestamp, + arbutil.SliceToUnsafePointer(data.contractAddress[:]), + arbutil.SliceToUnsafePointer(data.moduleHash[:]), + arbutil.SliceToUnsafePointer(data.msgSender[:]), + arbutil.SliceToUnsafePointer(data.msgValue[:]), + arbutil.SliceToUnsafePointer(data.txGasPrice[:]), + arbutil.SliceToUnsafePointer(data.txOrigin[:]), + arbmath.BoolToUint32(data.cached), + data.reentrant, + ) +} diff --git a/arbos/programs/wasmstorehelper.go b/arbos/programs/wasmstorehelper.go new file mode 100644 index 000000000..9e6917869 --- /dev/null +++ b/arbos/programs/wasmstorehelper.go @@ -0,0 +1,80 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + +package programs + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" +) + +// SaveActiveProgramToWasmStore is used to save active stylus programs to wasm store during rebuilding +func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash common.Hash, code []byte, time uint64, debugMode bool, rebuildingStartBlockTime uint64) error { + params, err := p.Params() + if err != nil { + return err + } + + program, err := p.getActiveProgram(codeHash, time, params) + if err != nil { + // The program is not active so return early + log.Info("program is not active, getActiveProgram returned error, hence do not include in rebuilding", "err", err) + return nil + } + + // It might happen that node crashed some time after rebuilding commenced and before it completed, hence when rebuilding + // resumes after node is restarted the latest diskdb derived from statedb might now have codehashes that were activated + // during the last rebuilding session. In such cases we don't need to fetch moduleshashes but instead return early + // since they would already be added to the wasm store + currentHoursSince := hoursSinceArbitrum(rebuildingStartBlockTime) + if currentHoursSince < program.activatedAt { + return nil + } + + moduleHash, err := p.moduleHashes.Get(codeHash) + if err != nil { + return err + } + + // If already in wasm store then return early + localAsm, err := statedb.TryGetActivatedAsm(moduleHash) + if err == nil && len(localAsm) > 0 { + return nil + } + + wasm, err := getWasmFromContractCode(code) + if err != nil { + log.Error("Failed to reactivate program while rebuilding wasm store: getWasmFromContractCode", "expected moduleHash", moduleHash, "err", err) + return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err) + } + + unlimitedGas := uint64(0xffffffffffff) + // We know program is activated, so it must be in correct version and not use too much memory + // Empty program address is supplied because we dont have access to this during rebuilding of wasm store + info, asm, module, err := activateProgramInternal(statedb, common.Address{}, codeHash, wasm, params.PageLimit, program.version, debugMode, &unlimitedGas) + if err != nil { + log.Error("failed to reactivate program while rebuilding wasm store", "expected moduleHash", moduleHash, "err", err) + return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err) + } + + if info.moduleHash != moduleHash { + log.Error("failed to reactivate program while rebuilding wasm store", "expected moduleHash", moduleHash, "got", info.moduleHash) + return fmt.Errorf("failed to reactivate program while rebuilding wasm store, expected ModuleHash: %v", moduleHash) + } + + batch := statedb.Database().WasmStore().NewBatch() + rawdb.WriteActivation(batch, moduleHash, asm, module) + if err := batch.Write(); err != nil { + log.Error("failed writing re-activation to state while rebuilding wasm store", "err", err) + return err + } + + return nil +} diff --git a/arbos/retryables/retryable.go b/arbos/retryables/retryable.go index 6984e4190..e1cfe48bc 100644 --- a/arbos/retryables/retryable.go +++ b/arbos/retryables/retryable.go @@ -145,7 +145,7 @@ func (rs *RetryableState) DeleteRetryable(id common.Hash, evm *vm.EVM, scenario escrowAddress := RetryableEscrowAddress(id) beneficiaryAddress := common.BytesToAddress(beneficiary[:]) amount := evm.StateDB.GetBalance(escrowAddress) - err = util.TransferBalance(&escrowAddress, &beneficiaryAddress, amount, evm, scenario, "escrow") + err = util.TransferBalance(&escrowAddress, &beneficiaryAddress, amount.ToBig(), evm, scenario, "escrow") if err != nil { return false, err } diff --git a/arbos/storage/storage.go b/arbos/storage/storage.go index 63987b91f..158b8896c 100644 --- a/arbos/storage/storage.go +++ b/arbos/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package storage @@ -6,6 +6,7 @@ package storage import ( "bytes" "fmt" + "math" "math/big" "sync/atomic" @@ -52,6 +53,7 @@ type Storage struct { const StorageReadCost = params.SloadGasEIP2200 const StorageWriteCost = params.SstoreSetGasEIP2200 const StorageWriteZeroCost = params.SstoreResetGasEIP2200 +const StorageCodeHashCost = params.ColdAccountAccessCostEIP2929 const storageKeyCacheSize = 1024 @@ -119,7 +121,12 @@ func (s *Storage) Get(key common.Hash) (common.Hash, error) { if info := s.burner.TracingInfo(); info != nil { info.RecordStorageGet(key) } - return s.db.GetState(s.account, s.mapAddress(key)), nil + return s.GetFree(key), nil +} + +// Gets a storage slot for free. Dangerous due to DoS potential. +func (s *Storage) GetFree(key common.Hash) common.Hash { + return s.db.GetState(s.account, s.mapAddress(key)) } func (s *Storage) GetStorageSlot(key common.Hash) common.Hash { @@ -139,6 +146,11 @@ func (s *Storage) GetUint64ByUint64(key uint64) (uint64, error) { return s.GetUint64(util.UintToHash(key)) } +func (s *Storage) GetUint32(key common.Hash) (uint32, error) { + value, err := s.Get(key) + return uint32(value.Big().Uint64()), err +} + func (s *Storage) Set(key common.Hash, value common.Hash) error { if s.burner.ReadOnly() { log.Error("Read-only burner attempted to mutate state", "key", key, "value", value) @@ -155,6 +167,10 @@ func (s *Storage) Set(key common.Hash, value common.Hash) error { return nil } +func (s *Storage) SetUint64(key common.Hash, value uint64) error { + return s.Set(key, util.UintToHash(value)) +} + func (s *Storage) SetByUint64(key uint64, value common.Hash) error { return s.Set(util.UintToHash(key), value) } @@ -163,6 +179,14 @@ func (s *Storage) SetUint64ByUint64(key uint64, value uint64) error { return s.Set(util.UintToHash(key), util.UintToHash(value)) } +func (s *Storage) SetUint32(key common.Hash, value uint32) error { + return s.Set(key, util.UintToHash(uint64(value))) +} + +func (s *Storage) SetByUint32(key uint32, value common.Hash) error { + return s.Set(util.UintToHash(uint64(key)), value) +} + func (s *Storage) Clear(key common.Hash) error { return s.Set(key, common.Hash{}) } @@ -280,6 +304,14 @@ func (s *Storage) ClearBytes() error { return s.ClearByUint64(0) } +func (s *Storage) GetCodeHash(address common.Address) (common.Hash, error) { + err := s.burner.Burn(StorageCodeHashCost) + if err != nil { + return common.Hash{}, err + } + return s.db.GetCodeHash(address), nil +} + func (s *Storage) Burner() burn.Burner { return s.burner // not public because these should never be changed once set } @@ -403,6 +435,86 @@ func (sbu *StorageBackedBips) Set(bips arbmath.Bips) error { return sbu.backing.Set(int64(bips)) } +// StorageBackedUBips represents an unsigned number of basis points +type StorageBackedUBips struct { + backing StorageBackedUint64 +} + +func (s *Storage) OpenStorageBackedUBips(offset uint64) StorageBackedUBips { + return StorageBackedUBips{StorageBackedUint64{s.NewSlot(offset)}} +} + +func (sbu *StorageBackedUBips) Get() (arbmath.UBips, error) { + value, err := sbu.backing.Get() + return arbmath.UBips(value), err +} + +func (sbu *StorageBackedUBips) Set(bips arbmath.UBips) error { + return sbu.backing.Set(bips.Uint64()) +} + +type StorageBackedUint16 struct { + StorageSlot +} + +func (s *Storage) OpenStorageBackedUint16(offset uint64) StorageBackedUint16 { + return StorageBackedUint16{s.NewSlot(offset)} +} + +func (sbu *StorageBackedUint16) Get() (uint16, error) { + raw, err := sbu.StorageSlot.Get() + big := raw.Big() + if !big.IsUint64() || big.Uint64() > math.MaxUint16 { + panic("expected uint16 compatible value in storage") + } + return uint16(big.Uint64()), err +} + +func (sbu *StorageBackedUint16) Set(value uint16) error { + bigValue := new(big.Int).SetUint64(uint64(value)) + return sbu.StorageSlot.Set(common.BigToHash(bigValue)) +} + +type StorageBackedUint24 struct { + StorageSlot +} + +func (s *Storage) OpenStorageBackedUint24(offset uint64) StorageBackedUint24 { + return StorageBackedUint24{s.NewSlot(offset)} +} + +func (sbu *StorageBackedUint24) Get() (arbmath.Uint24, error) { + raw, err := sbu.StorageSlot.Get() + value := arbmath.BigToUint24OrPanic(raw.Big()) + return value, err +} + +func (sbu *StorageBackedUint24) Set(value arbmath.Uint24) error { + return sbu.StorageSlot.Set(common.BigToHash(value.ToBig())) +} + +type StorageBackedUint32 struct { + StorageSlot +} + +func (s *Storage) OpenStorageBackedUint32(offset uint64) StorageBackedUint32 { + return StorageBackedUint32{s.NewSlot(offset)} +} + +func (sbu *StorageBackedUint32) Get() (uint32, error) { + raw, err := sbu.StorageSlot.Get() + big := raw.Big() + if !big.IsUint64() || big.Uint64() > math.MaxUint32 { + panic("expected uint32 compatible value in storage") + } + return uint32(big.Uint64()), err +} + +func (sbu *StorageBackedUint32) Set(value uint32) error { + bigValue := new(big.Int).SetUint64(uint64(value)) + return sbu.StorageSlot.Set(common.BigToHash(bigValue)) +} + type StorageBackedUint64 struct { StorageSlot } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 569edb7c6..d3ca790ce 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbos @@ -8,10 +8,10 @@ import ( "fmt" "math/big" + "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/util" - "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/core/types" @@ -41,8 +41,9 @@ type TxProcessor struct { posterGas uint64 computeHoldGas uint64 // amount of gas temporarily held to prevent compute from exceeding the gas limit delayedInbox bool // whether this tx was submitted through the delayed inbox - Callers []common.Address - TopTxType *byte // set once in StartTxHook + Contracts []*vm.Contract + Programs map[common.Address]uint // # of distinct context spans for each program + TopTxType *byte // set once in StartTxHook evm *vm.EVM CurrentRetryable *common.Hash CurrentRefundTo *common.Address @@ -62,7 +63,8 @@ func NewTxProcessor(evm *vm.EVM, msg *core.Message) *TxProcessor { PosterFee: new(big.Int), posterGas: 0, delayedInbox: evm.Context.Coinbase != l1pricing.BatchPosterAddress, - Callers: []common.Address{}, + Contracts: []*vm.Contract{}, + Programs: make(map[common.Address]uint), TopTxType: nil, evm: evm, CurrentRetryable: nil, @@ -72,12 +74,22 @@ func NewTxProcessor(evm *vm.EVM, msg *core.Message) *TxProcessor { } } -func (p *TxProcessor) PushCaller(addr common.Address) { - p.Callers = append(p.Callers, addr) +func (p *TxProcessor) PushContract(contract *vm.Contract) { + p.Contracts = append(p.Contracts, contract) + + if !contract.IsDelegateOrCallcode() { + p.Programs[contract.Address()]++ + } } -func (p *TxProcessor) PopCaller() { - p.Callers = p.Callers[:len(p.Callers)-1] +func (p *TxProcessor) PopContract() { + newLen := len(p.Contracts) - 1 + popped := p.Contracts[newLen] + p.Contracts = p.Contracts[:newLen] + + if !popped.IsDelegateOrCallcode() { + p.Programs[popped.Address()]-- + } } // Attempts to subtract up to `take` from `pool` without going negative. @@ -95,6 +107,30 @@ func takeFunds(pool *big.Int, take *big.Int) *big.Int { return new(big.Int).Set(take) } +func (p *TxProcessor) ExecuteWASM(scope *vm.ScopeContext, input []byte, interpreter *vm.EVMInterpreter) ([]byte, error) { + contract := scope.Contract + acting := contract.Address() + + var tracingInfo *util.TracingInfo + if interpreter.Config().Tracer != nil { + caller := contract.CallerAddress + tracingInfo = util.NewTracingInfo(interpreter.Evm(), caller, acting, util.TracingDuringEVM) + } + + // reentrant if more than one open same-actor context span exists + reentrant := p.Programs[acting] > 1 + + return p.state.Programs().CallProgram( + scope, + p.evm.StateDB, + interpreter, + tracingInfo, + input, + reentrant, + p.RunMode(), + ) +} + func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, returnData []byte) { // This hook is called before gas charging and will end the state transition if endTxNow is set to true // Hence, we must charge for any l2 resources if endTxNow is returned true @@ -143,7 +179,9 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r // We intentionally use the variant here that doesn't do tracing, // because this transfer is represented as the outer eth transaction. // This transfer is necessary because we don't actually invoke the EVM. - core.Transfer(evm.StateDB, from, *to, value) + // Since MintBalance already called AddBalance on `from`, + // we don't have EIP-161 concerns around not touching `from`. + core.Transfer(evm.StateDB, from, *to, uint256.MustFromBig(value)) return true, 0, nil, nil case *types.ArbitrumInternalTx: defer (startTracer())() @@ -172,7 +210,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r // check that the user has enough balance to pay for the max submission fee balanceAfterMint := evm.StateDB.GetBalance(tx.From) - if balanceAfterMint.Cmp(tx.MaxSubmissionFee) < 0 { + if balanceAfterMint.ToBig().Cmp(tx.MaxSubmissionFee) < 0 { err := fmt.Errorf( "insufficient funds for max submission fee: address %v have %v want %v", tx.From, balanceAfterMint, tx.MaxSubmissionFee, @@ -245,18 +283,13 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r } balance := statedb.GetBalance(tx.From) + // evm.Context.BaseFee is already lowered to 0 when vm runs with NoBaseFee flag and 0 gas price effectiveBaseFee := evm.Context.BaseFee usergas := p.msg.GasLimit - if p.msg.TxRunMode != core.MessageCommitMode && p.msg.GasFeeCap.BitLen() == 0 { - // In gas estimation or eth_call mode, we permit a zero gas fee cap. - // This matches behavior with normal tx gas estimation and eth_call. - effectiveBaseFee = common.Big0 - } - maxGasCost := arbmath.BigMulByUint(tx.GasFeeCap, usergas) maxFeePerGasTooLow := arbmath.BigLessThan(tx.GasFeeCap, effectiveBaseFee) - if arbmath.BigLessThan(balance, maxGasCost) || usergas < params.TxGas || maxFeePerGasTooLow { + if arbmath.BigLessThan(balance.ToBig(), maxGasCost) || usergas < params.TxGas || maxFeePerGasTooLow { // User either specified too low of a gas fee cap, didn't have enough balance to pay for gas, // or the specified gas limit is below the minimum transaction gas cost. // Either way, attempt to refund the gas costs, since we're not doing the auto-redeem. @@ -395,16 +428,21 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err var gasNeededToStartEVM uint64 tipReceipient, _ := p.state.NetworkFeeAccount() - basefee := p.evm.Context.BaseFee + var basefee *big.Int + if p.evm.Context.BaseFeeInBlock != nil { + basefee = p.evm.Context.BaseFeeInBlock + } else { + basefee = p.evm.Context.BaseFee + } var poster common.Address - if p.msg.TxRunMode != core.MessageCommitMode { + if !p.msg.TxRunMode.ExecutedOnChain() { poster = l1pricing.BatchPosterAddress } else { poster = p.evm.Context.Coinbase } - if p.msg.TxRunMode == core.MessageCommitMode { + if p.msg.TxRunMode.ExecutedOnChain() { p.msg.SkipL1Charging = false } if basefee.Sign() > 0 && !p.msg.SkipL1Charging { @@ -442,6 +480,10 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err return tipReceipient, nil } +func (p *TxProcessor) RunMode() core.MessageRunMode { + return p.msg.TxRunMode +} + func (p *TxProcessor) NonrefundableGas() uint64 { // EVM-incentivized activity like freeing storage should only refund amounts paid to the network address, // which represents the overall burden to node operators. A poster's costs, then, should not be eligible @@ -467,7 +509,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { if underlyingTx != nil && underlyingTx.Type() == types.ArbitrumRetryTxType { inner, _ := underlyingTx.GetInner().(*types.ArbitrumRetryTx) effectiveBaseFee := inner.GasFeeCap - if p.msg.TxRunMode == core.MessageCommitMode && !arbmath.BigEquals(effectiveBaseFee, p.evm.Context.BaseFee) { + if p.msg.TxRunMode.ExecutedOnChain() && !arbmath.BigEquals(effectiveBaseFee, p.evm.Context.BaseFee) { log.Error( "ArbitrumRetryTx GasFeeCap doesn't match basefee in commit mode", "txHash", underlyingTx.Hash(), @@ -548,11 +590,16 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { } } // we've already credited the network fee account, but we didn't charge the gas pool yet - p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast(gasUsed))) + p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast[int64](gasUsed))) return } - basefee := p.evm.Context.BaseFee + var basefee *big.Int + if p.evm.Context.BaseFeeInBlock != nil { + basefee = p.evm.Context.BaseFeeInBlock + } else { + basefee = p.evm.Context.BaseFee + } totalCost := arbmath.BigMul(basefee, arbmath.UintToBig(gasUsed)) // total cost = price of gas * gas burnt computeCost := arbmath.BigSub(totalCost, p.PosterFee) // total cost = network's compute + poster's L1 costs if computeCost.Sign() < 0 { @@ -607,29 +654,23 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { log.Error("total gas used < poster gas component", "gasUsed", gasUsed, "posterGas", p.posterGas) computeGas = gasUsed } - p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast(computeGas))) + p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast[int64](computeGas))) } } func (p *TxProcessor) ScheduledTxes() types.Transactions { scheduled := types.Transactions{} time := p.evm.Context.Time + // p.evm.Context.BaseFee is already lowered to 0 when vm runs with NoBaseFee flag and 0 gas price effectiveBaseFee := p.evm.Context.BaseFee chainID := p.evm.ChainConfig().ChainID - if p.msg.TxRunMode != core.MessageCommitMode && p.msg.GasFeeCap.BitLen() == 0 { - // In gas estimation or eth_call mode, we permit a zero gas fee cap. - // This matches behavior with normal tx gas estimation and eth_call. - effectiveBaseFee = common.Big0 - } - logs := p.evm.StateDB.GetCurrentTxLogs() for _, log := range logs { if log.Address != ArbRetryableTxAddress || log.Topics[0] != RedeemScheduledEventID { continue } - event := &precompilesgen.ArbRetryableTxRedeemScheduled{} - err := util.ParseRedeemScheduledLog(event, log) + event, err := util.ParseRedeemScheduledLog(log) if err != nil { glog.Error("Failed to parse RedeemScheduled log", "err", err) continue @@ -697,10 +738,8 @@ func (p *TxProcessor) GetPaidGasPrice() *big.Int { gasPrice := p.evm.GasPrice version := p.state.ArbOSVersion() if version != 9 { + // p.evm.Context.BaseFee is already lowered to 0 when vm runs with NoBaseFee flag and 0 gas price gasPrice = p.evm.Context.BaseFee - if p.msg.TxRunMode != core.MessageCommitMode && p.msg.GasFeeCap.Sign() == 0 { - gasPrice = common.Big0 - } } return gasPrice } diff --git a/arbos/util/tracing.go b/arbos/util/tracing.go index e4cde0f42..49b82d6d6 100644 --- a/arbos/util/tracing.go +++ b/arbos/util/tracing.go @@ -42,7 +42,7 @@ func NewTracingInfo(evm *vm.EVM, from, to common.Address, scenario TracingScenar return &TracingInfo{ Tracer: evm.Config.Tracer, Scenario: scenario, - Contract: vm.NewContract(addressHolder{to}, addressHolder{from}, big.NewInt(0), 0), + Contract: vm.NewContract(addressHolder{to}, addressHolder{from}, uint256.NewInt(0), 0), Depth: evm.Depth(), } } @@ -79,7 +79,7 @@ func (info *TracingInfo) MockCall(input []byte, gas uint64, from, to common.Addr tracer := info.Tracer depth := info.Depth - contract := vm.NewContract(addressHolder{to}, addressHolder{from}, amount, gas) + contract := vm.NewContract(addressHolder{to}, addressHolder{from}, uint256.MustFromBig(amount), gas) scope := &vm.ScopeContext{ Memory: TracingMemoryFromBytes(input), diff --git a/arbos/util/transfer.go b/arbos/util/transfer.go index 3a8118120..e293ef13c 100644 --- a/arbos/util/transfer.go +++ b/arbos/util/transfer.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -29,13 +30,17 @@ func TransferBalance( } if from != nil { balance := evm.StateDB.GetBalance(*from) - if arbmath.BigLessThan(balance, amount) { + if arbmath.BigLessThan(balance.ToBig(), amount) { return fmt.Errorf("%w: addr %v have %v want %v", vm.ErrInsufficientBalance, *from, balance, amount) } - evm.StateDB.SubBalance(*from, amount) + evm.StateDB.SubBalance(*from, uint256.MustFromBig(amount)) + if evm.Context.ArbOSVersion >= 30 { + // ensure the from account is "touched" for EIP-161 + evm.StateDB.AddBalance(*from, &uint256.Int{}) + } } if to != nil { - evm.StateDB.AddBalance(*to, amount) + evm.StateDB.AddBalance(*to, uint256.MustFromBig(amount)) } if tracer := evm.Config.Tracer; tracer != nil { if evm.Depth() != 0 && scenario != TracingDuringEVM { @@ -59,7 +64,7 @@ func TransferBalance( info := &TracingInfo{ Tracer: evm.Config.Tracer, Scenario: scenario, - Contract: vm.NewContract(addressHolder{*to}, addressHolder{*from}, big.NewInt(0), 0), + Contract: vm.NewContract(addressHolder{*to}, addressHolder{*from}, uint256.NewInt(0), 0), Depth: evm.Depth(), } info.MockCall([]byte{}, 0, *from, *to, amount) diff --git a/arbos/util/util.go b/arbos/util/util.go index 4c0142aeb..69d90171a 100644 --- a/arbos/util/util.go +++ b/arbos/util/util.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package util @@ -15,14 +15,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + pgen "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" ) var AddressAliasOffset *big.Int var InverseAddressAliasOffset *big.Int -var ParseRedeemScheduledLog func(interface{}, *types.Log) error -var ParseL2ToL1TransactionLog func(interface{}, *types.Log) error -var ParseL2ToL1TxLog func(interface{}, *types.Log) error +var ParseRedeemScheduledLog func(*types.Log) (*pgen.ArbRetryableTxRedeemScheduled, error) +var ParseL2ToL1TransactionLog func(*types.Log) (*pgen.ArbSysL2ToL1Transaction, error) +var ParseL2ToL1TxLog func(*types.Log) (*pgen.ArbSysL2ToL1Tx, error) var PackInternalTxDataStartBlock func(...interface{}) ([]byte, error) var UnpackInternalTxDataStartBlock func([]byte) (map[string]interface{}, error) var PackInternalTxDataBatchPostingReport func(...interface{}) ([]byte, error) @@ -37,63 +38,64 @@ func init() { AddressAliasOffset = offset InverseAddressAliasOffset = arbmath.BigSub(new(big.Int).Lsh(big.NewInt(1), 160), AddressAliasOffset) - // Create a mechanism for parsing event logs - logParser := func(source string, name string) func(interface{}, *types.Log) error { - precompile, err := abi.JSON(strings.NewReader(source)) - if err != nil { - panic(fmt.Sprintf("failed to parse ABI for %s: %s", name, err)) - } - inputs := precompile.Events[name].Inputs - indexed := abi.Arguments{} - for _, input := range inputs { - if input.Indexed { - indexed = append(indexed, input) - } - } + ParseRedeemScheduledLog = NewLogParser[pgen.ArbRetryableTxRedeemScheduled](pgen.ArbRetryableTxABI, "RedeemScheduled") + ParseL2ToL1TxLog = NewLogParser[pgen.ArbSysL2ToL1Tx](pgen.ArbSysABI, "L2ToL1Tx") + ParseL2ToL1TransactionLog = NewLogParser[pgen.ArbSysL2ToL1Transaction](pgen.ArbSysABI, "L2ToL1Transaction") + + acts := precompilesgen.ArbosActsABI + PackInternalTxDataStartBlock, UnpackInternalTxDataStartBlock = NewCallParser(acts, "startBlock") + PackInternalTxDataBatchPostingReport, UnpackInternalTxDataBatchPostingReport = NewCallParser(acts, "batchPostingReport") + PackArbRetryableTxRedeem, _ = NewCallParser(precompilesgen.ArbRetryableTxABI, "redeem") +} - return func(event interface{}, log *types.Log) error { - unpacked, err := inputs.Unpack(log.Data) - if err != nil { - return err - } - if err := inputs.Copy(event, unpacked); err != nil { - return err - } - return abi.ParseTopics(event, indexed, log.Topics[1:]) +// Create a mechanism for packing and unpacking calls +func NewCallParser(source string, name string) (func(...interface{}) ([]byte, error), func([]byte) (map[string]interface{}, error)) { + contract, err := abi.JSON(strings.NewReader(source)) + if err != nil { + panic(fmt.Sprintf("failed to parse ABI for %s: %s", name, err)) + } + method, ok := contract.Methods[name] + if !ok { + panic(fmt.Sprintf("method %v does not exist", name)) + } + pack := func(args ...interface{}) ([]byte, error) { + return contract.Pack(name, args...) + } + unpack := func(data []byte) (map[string]interface{}, error) { + if len(data) < 4 { + return nil, errors.New("data not long enough") } + args := make(map[string]interface{}) + return args, method.Inputs.UnpackIntoMap(args, data[4:]) } + return pack, unpack +} - // Create a mechanism for packing and unpacking calls - callParser := func(source string, name string) (func(...interface{}) ([]byte, error), func([]byte) (map[string]interface{}, error)) { - contract, err := abi.JSON(strings.NewReader(source)) - if err != nil { - panic(fmt.Sprintf("failed to parse ABI for %s: %s", name, err)) - } - method, ok := contract.Methods[name] - if !ok { - panic(fmt.Sprintf("method %v does not exist", name)) +// Create a mechanism for parsing event logs +func NewLogParser[T any](source string, name string) func(*types.Log) (*T, error) { + precompile, err := abi.JSON(strings.NewReader(source)) + if err != nil { + panic(fmt.Sprintf("failed to parse ABI for %s: %s", name, err)) + } + inputs := precompile.Events[name].Inputs + indexed := abi.Arguments{} + for _, input := range inputs { + if input.Indexed { + indexed = append(indexed, input) } - pack := func(args ...interface{}) ([]byte, error) { - return contract.Pack(name, args...) + } + return func(log *types.Log) (*T, error) { + unpacked, err := inputs.Unpack(log.Data) + if err != nil { + return nil, err } - unpack := func(data []byte) (map[string]interface{}, error) { - if len(data) < 4 { - return nil, errors.New("data not long enough") - } - args := make(map[string]interface{}) - return args, method.Inputs.UnpackIntoMap(args, data[4:]) + var event T + if err := inputs.Copy(&event, unpacked); err != nil { + return nil, err } - return pack, unpack + err = abi.ParseTopics(&event, indexed, log.Topics[1:]) + return &event, err } - - ParseRedeemScheduledLog = logParser(precompilesgen.ArbRetryableTxABI, "RedeemScheduled") - ParseL2ToL1TxLog = logParser(precompilesgen.ArbSysABI, "L2ToL1Tx") - ParseL2ToL1TransactionLog = logParser(precompilesgen.ArbSysABI, "L2ToL1Transaction") - - acts := precompilesgen.ArbosActsABI - PackInternalTxDataStartBlock, UnpackInternalTxDataStartBlock = callParser(acts, "startBlock") - PackInternalTxDataBatchPostingReport, UnpackInternalTxDataBatchPostingReport = callParser(acts, "batchPostingReport") - PackArbRetryableTxRedeem, _ = callParser(precompilesgen.ArbRetryableTxABI, "redeem") } func AddressToHash(address common.Address) common.Hash { @@ -196,7 +198,7 @@ func UintToHash(val uint64) common.Hash { } func HashPlusInt(x common.Hash, y int64) common.Hash { - return common.BigToHash(new(big.Int).Add(x.Big(), big.NewInt(y))) //BUGBUG: BigToHash(x) converts abs(x) to a Hash + return common.BigToHash(new(big.Int).Add(x.Big(), big.NewInt(y))) // BUGBUG: BigToHash(x) converts abs(x) to a Hash } func RemapL1Address(l1Addr common.Address) common.Address { diff --git a/arbstate/daprovider/reader.go b/arbstate/daprovider/reader.go new file mode 100644 index 000000000..560af3af1 --- /dev/null +++ b/arbstate/daprovider/reader.go @@ -0,0 +1,104 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package daprovider + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/blobs" +) + +type Reader interface { + // IsValidHeaderByte returns true if the given headerByte has bits corresponding to the DA provider + IsValidHeaderByte(headerByte byte) bool + + // RecoverPayloadFromBatch fetches the underlying payload from the DA provider given the batch header information + RecoverPayloadFromBatch( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + sequencerMsg []byte, + preimageRecorder PreimageRecorder, + validateSeqMsg bool, + ) ([]byte, error) +} + +// NewReaderForDAS is generally meant to be only used by nitro. +// DA Providers should implement methods in the Reader interface independently +func NewReaderForDAS(dasReader DASReader) *readerForDAS { + return &readerForDAS{dasReader: dasReader} +} + +type readerForDAS struct { + dasReader DASReader +} + +func (d *readerForDAS) IsValidHeaderByte(headerByte byte) bool { + return IsDASMessageHeaderByte(headerByte) +} + +func (d *readerForDAS) RecoverPayloadFromBatch( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + sequencerMsg []byte, + preimageRecorder PreimageRecorder, + validateSeqMsg bool, +) ([]byte, error) { + return RecoverPayloadFromDasBatch(ctx, batchNum, sequencerMsg, d.dasReader, preimageRecorder, validateSeqMsg) +} + +// NewReaderForBlobReader is generally meant to be only used by nitro. +// DA Providers should implement methods in the Reader interface independently +func NewReaderForBlobReader(blobReader BlobReader) *readerForBlobReader { + return &readerForBlobReader{blobReader: blobReader} +} + +type readerForBlobReader struct { + blobReader BlobReader +} + +func (b *readerForBlobReader) IsValidHeaderByte(headerByte byte) bool { + return IsBlobHashesHeaderByte(headerByte) +} + +func (b *readerForBlobReader) RecoverPayloadFromBatch( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + sequencerMsg []byte, + preimageRecorder PreimageRecorder, + validateSeqMsg bool, +) ([]byte, error) { + blobHashes := sequencerMsg[41:] + if len(blobHashes)%len(common.Hash{}) != 0 { + return nil, ErrInvalidBlobDataFormat + } + versionedHashes := make([]common.Hash, len(blobHashes)/len(common.Hash{})) + for i := 0; i*32 < len(blobHashes); i += 1 { + copy(versionedHashes[i][:], blobHashes[i*32:(i+1)*32]) + } + kzgBlobs, err := b.blobReader.GetBlobs(ctx, batchBlockHash, versionedHashes) + if err != nil { + return nil, fmt.Errorf("failed to get blobs: %w", err) + } + if preimageRecorder != nil { + for i, blob := range kzgBlobs { + // Prevent aliasing `blob` when slicing it, as for range loops overwrite the same variable + // Won't be necessary after Go 1.22 with https://go.dev/blog/loopvar-preview + b := blob + preimageRecorder(versionedHashes[i], b[:], arbutil.EthVersionedHashPreimageType) + } + } + payload, err := blobs.DecodeBlobs(kzgBlobs) + if err != nil { + log.Warn("Failed to decode blobs", "batchBlockHash", batchBlockHash, "versionedHashes", versionedHashes, "err", err) + return nil, nil + } + return payload, nil +} diff --git a/arbstate/das_reader.go b/arbstate/daprovider/util.go similarity index 62% rename from arbstate/das_reader.go rename to arbstate/daprovider/util.go index f131a5360..7d8f1a404 100644 --- a/arbstate/das_reader.go +++ b/arbstate/daprovider/util.go @@ -1,7 +1,7 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -package arbstate +package daprovider import ( "bufio" @@ -13,18 +13,53 @@ import ( "io" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/das/dastree" ) -type DataAvailabilityReader interface { +type DASReader interface { GetByHash(ctx context.Context, hash common.Hash) ([]byte, error) ExpirationPolicy(ctx context.Context) (ExpirationPolicy, error) } -var ErrHashMismatch = errors.New("result does not match expected hash") +type DASWriter interface { + // Store requests that the message be stored until timeout (UTC time in unix epoch seconds). + Store(ctx context.Context, message []byte, timeout uint64) (*DataAvailabilityCertificate, error) + fmt.Stringer +} + +type BlobReader interface { + GetBlobs( + ctx context.Context, + batchBlockHash common.Hash, + versionedHashes []common.Hash, + ) ([]kzg4844.Blob, error) + Initialize(ctx context.Context) error +} + +// PreimageRecorder is used to add (key,value) pair to the map accessed by key = ty of a bigger map, preimages. +// If ty doesn't exist as a key in the preimages map, then it is intialized to map[common.Hash][]byte and then (key,value) pair is added +type PreimageRecorder func(key common.Hash, value []byte, ty arbutil.PreimageType) + +// RecordPreimagesTo takes in preimages map and returns a function that can be used +// In recording (hash,preimage) key value pairs into preimages map, when fetching payload through RecoverPayloadFromBatch +func RecordPreimagesTo(preimages map[arbutil.PreimageType]map[common.Hash][]byte) PreimageRecorder { + if preimages == nil { + return nil + } + return func(key common.Hash, value []byte, ty arbutil.PreimageType) { + if preimages[ty] == nil { + preimages[ty] = make(map[common.Hash][]byte) + } + preimages[ty][key] = value + } +} // DASMessageHeaderFlag indicates that this data is a certificate for the data availability service, // which will retrieve the full batch data. @@ -83,6 +118,114 @@ func IsKnownHeaderByte(b uint8) bool { return b&^KnownHeaderBits == 0 } +const MinLifetimeSecondsForDataAvailabilityCert = 7 * 24 * 60 * 60 // one week +var ( + ErrHashMismatch = errors.New("result does not match expected hash") + ErrBatchToDasFailed = errors.New("unable to batch to DAS") + ErrNoBlobReader = errors.New("blob batch payload was encountered but no BlobReader was configured") + ErrInvalidBlobDataFormat = errors.New("blob batch data is not a list of hashes as expected") + ErrSeqMsgValidation = errors.New("error validating recovered payload from batch") +) + +type KeysetValidationMode uint8 + +const KeysetValidate KeysetValidationMode = 0 +const KeysetPanicIfInvalid KeysetValidationMode = 1 +const KeysetDontValidate KeysetValidationMode = 2 + +func RecoverPayloadFromDasBatch( + ctx context.Context, + batchNum uint64, + sequencerMsg []byte, + dasReader DASReader, + preimageRecorder PreimageRecorder, + validateSeqMsg bool, +) ([]byte, error) { + cert, err := DeserializeDASCertFrom(bytes.NewReader(sequencerMsg[40:])) + if err != nil { + log.Error("Failed to deserialize DAS message", "err", err) + return nil, nil + } + version := cert.Version + + if version >= 2 { + log.Error("Your node software is probably out of date", "certificateVersion", version) + return nil, nil + } + + getByHash := func(ctx context.Context, hash common.Hash) ([]byte, error) { + newHash := hash + if version == 0 { + newHash = dastree.FlatHashToTreeHash(hash) + } + + preimage, err := dasReader.GetByHash(ctx, newHash) + if err != nil && hash != newHash { + log.Debug("error fetching new style hash, trying old", "new", newHash, "old", hash, "err", err) + preimage, err = dasReader.GetByHash(ctx, hash) + } + if err != nil { + return nil, err + } + + switch { + case version == 0 && crypto.Keccak256Hash(preimage) != hash: + fallthrough + case version == 1 && dastree.Hash(preimage) != hash: + log.Error( + "preimage mismatch for hash", + "hash", hash, "err", ErrHashMismatch, "version", version, + ) + return nil, ErrHashMismatch + } + return preimage, nil + } + + keysetPreimage, err := getByHash(ctx, cert.KeysetHash) + if err != nil { + log.Error("Couldn't get keyset", "err", err) + return nil, err + } + if preimageRecorder != nil { + dastree.RecordHash(preimageRecorder, keysetPreimage) + } + + keyset, err := DeserializeKeyset(bytes.NewReader(keysetPreimage), !validateSeqMsg) + if err != nil { + return nil, fmt.Errorf("%w. Couldn't deserialize keyset, err: %w, keyset hash: %x batch num: %d", ErrSeqMsgValidation, err, cert.KeysetHash, batchNum) + } + err = keyset.VerifySignature(cert.SignersMask, cert.SerializeSignableFields(), cert.Sig) + if err != nil { + log.Error("Bad signature on DAS batch", "err", err) + return nil, nil + } + + maxTimestamp := binary.BigEndian.Uint64(sequencerMsg[8:16]) + if cert.Timeout < maxTimestamp+MinLifetimeSecondsForDataAvailabilityCert { + log.Error("Data availability cert expires too soon", "err", "") + return nil, nil + } + + dataHash := cert.DataHash + payload, err := getByHash(ctx, dataHash) + if err != nil { + log.Error("Couldn't fetch DAS batch contents", "err", err) + return nil, err + } + + if preimageRecorder != nil { + if version == 0 { + treeLeaf := dastree.FlatHashToTreeLeaf(dataHash) + preimageRecorder(dataHash, payload, arbutil.Keccak256PreimageType) + preimageRecorder(crypto.Keccak256Hash(treeLeaf), treeLeaf, arbutil.Keccak256PreimageType) + } else { + dastree.RecordHash(preimageRecorder, payload) + } + } + + return payload, nil +} + type DataAvailabilityCertificate struct { KeysetHash [32]byte DataHash [32]byte @@ -167,7 +310,7 @@ func (c *DataAvailabilityCertificate) SerializeSignableFields() []byte { func (c *DataAvailabilityCertificate) RecoverKeyset( ctx context.Context, - da DataAvailabilityReader, + da DASReader, assumeKeysetValid bool, ) (*DataAvailabilityKeyset, error) { keysetBytes, err := da.GetByHash(ctx, c.KeysetHash) @@ -316,3 +459,22 @@ func StringToExpirationPolicy(s string) (ExpirationPolicy, error) { return -1, fmt.Errorf("invalid Expiration Policy: %s", s) } } + +func Serialize(c *DataAvailabilityCertificate) []byte { + + flags := DASMessageHeaderFlag + if c.Version != 0 { + flags |= TreeDASMessageHeaderFlag + } + + buf := make([]byte, 0) + buf = append(buf, flags) + buf = append(buf, c.KeysetHash[:]...) + buf = append(buf, c.SerializeSignableFields()...) + + var intData [8]byte + binary.BigEndian.PutUint64(intData[:], c.SignersMask) + buf = append(buf, intData[:]...) + + return append(buf, blsSignatures.SignatureToBytes(c.Sig)...) +} diff --git a/arbstate/daprovider/writer.go b/arbstate/daprovider/writer.go new file mode 100644 index 000000000..a26e53c94 --- /dev/null +++ b/arbstate/daprovider/writer.go @@ -0,0 +1,47 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package daprovider + +import ( + "context" + "errors" + + "github.com/ethereum/go-ethereum/log" +) + +type Writer interface { + // Store posts the batch data to the invoking DA provider + // And returns sequencerMsg which is later used to retrieve the batch data + Store( + ctx context.Context, + message []byte, + timeout uint64, + disableFallbackStoreDataOnChain bool, + ) ([]byte, error) +} + +// DAProviderWriterForDAS is generally meant to be only used by nitro. +// DA Providers should implement methods in the DAProviderWriter interface independently +func NewWriterForDAS(dasWriter DASWriter) *writerForDAS { + return &writerForDAS{dasWriter: dasWriter} +} + +type writerForDAS struct { + dasWriter DASWriter +} + +func (d *writerForDAS) Store(ctx context.Context, message []byte, timeout uint64, disableFallbackStoreDataOnChain bool) ([]byte, error) { + cert, err := d.dasWriter.Store(ctx, message, timeout) + if errors.Is(err, ErrBatchToDasFailed) { + if disableFallbackStoreDataOnChain { + return nil, errors.New("unable to batch to DAS and fallback storing data on chain is disabled") + } + log.Warn("Falling back to storing data on chain", "err", err) + return message, nil + } else if err != nil { + return nil, err + } else { + return Serialize(cert), nil + } +} diff --git a/arbstate/inbox.go b/arbstate/inbox.go index 8c8001715..a66359063 100644 --- a/arbstate/inbox.go +++ b/arbstate/inbox.go @@ -14,9 +14,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -24,10 +21,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l1pricing" - "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/das/dastree" - "github.com/offchainlabs/nitro/eigenda" - "github.com/offchainlabs/nitro/util/blobs" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/zeroheavy" ) @@ -43,15 +37,6 @@ type InboxBackend interface { ReadDelayedInbox(seqNum uint64) (*arbostypes.L1IncomingMessage, error) } -type BlobReader interface { - GetBlobs( - ctx context.Context, - batchBlockHash common.Hash, - versionedHashes []common.Hash, - ) ([]kzg4844.Blob, error) - Initialize(ctx context.Context) error -} - type sequencerMessage struct { minTimestamp uint64 maxTimestamp uint64 @@ -64,14 +49,8 @@ type sequencerMessage struct { const MaxDecompressedLen int = 1024 * 1024 * 16 // 16 MiB const maxZeroheavyDecompressedLen = 101*MaxDecompressedLen/100 + 64 const MaxSegmentsPerSequencerMessage = 100 * 1024 -const MinLifetimeSecondsForDataAvailabilityCert = 7 * 24 * 60 * 60 // one week - -var ( - ErrNoBlobReader = errors.New("blob batch payload was encountered but no BlobReader was configured") - ErrInvalidBlobDataFormat = errors.New("blob batch data is not a list of hashes as expected") -) -func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash common.Hash, data []byte, daProviders []DataAvailabilityProvider, keysetValidationMode KeysetValidationMode) (*sequencerMessage, error) { +func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash common.Hash, data []byte, dapReaders []daprovider.Reader, keysetValidationMode daprovider.KeysetValidationMode) (*sequencerMessage, error) { if len(data) < 40 { return nil, errors.New("sequencer message missing L1 header") } @@ -94,25 +73,33 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash // If the parent chain sequencer inbox smart contract authenticated this batch, // an unknown header byte must mean that this node is out of date, // because the smart contract understands the header byte and this node doesn't. - if len(payload) > 0 && IsL1AuthenticatedMessageHeaderByte(payload[0]) && !IsKnownHeaderByte(payload[0]) && !eigenda.IsEigenDAMessageHeaderByte(payload[0]) { + if len(payload) > 0 && daprovider.IsL1AuthenticatedMessageHeaderByte(payload[0]) && !daprovider.IsKnownHeaderByte(payload[0]) { return nil, fmt.Errorf("%w: batch has unsupported authenticated header byte 0x%02x", arbosState.ErrFatalNodeOutOfDate, payload[0]) } // Stage 1: Extract the payload from any data availability header. // It's important that multiple DAS strategies can't both be invoked in the same batch, // as these headers are validated by the sequencer inbox and not other DASs. - // We try to extract payload from the first occuring valid DA provider in the daProviders list + // We try to extract payload from the first occuring valid DA reader in the dapReaders list if len(payload) > 0 { println("looking for DA provider") foundDA := false var err error - - for _, provider := range daProviders { - println(fmt.Sprintf("Reading message from provider: %v", provider)) - if provider != nil && provider.IsValidHeaderByte(payload[0]) { - payload, err = provider.RecoverPayloadFromBatch(ctx, batchNum, batchBlockHash, data, nil, keysetValidationMode) + for _, dapReader := range dapReaders { + if dapReader != nil && dapReader.IsValidHeaderByte(payload[0]) { + payload, err = dapReader.RecoverPayloadFromBatch(ctx, batchNum, batchBlockHash, data, nil, keysetValidationMode != daprovider.KeysetDontValidate) if err != nil { - return nil, err + // Matches the way keyset validation was done inside DAS readers i.e logging the error + // But other daproviders might just want to return the error + if errors.Is(err, daprovider.ErrSeqMsgValidation) && daprovider.IsDASMessageHeaderByte(payload[0]) { + logLevel := log.Error + if keysetValidationMode == daprovider.KeysetPanicIfInvalid { + logLevel = log.Crit + } + logLevel(err.Error()) + } else { + return nil, err + } } if payload == nil { return parsedMsg, nil @@ -123,12 +110,10 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash } if !foundDA { - if IsDASMessageHeaderByte(payload[0]) { + if daprovider.IsDASMessageHeaderByte(payload[0]) { log.Error("No DAS Reader configured, but sequencer message found with DAS header") - } else if IsBlobHashesHeaderByte(payload[0]) { - return nil, ErrNoBlobReader - } else if eigenda.IsEigenDAMessageHeaderByte(payload[0]) { - log.Error("eigenDA versioned batch payload was encountered but no instance of EigenDA was configured") + } else if daprovider.IsBlobHashesHeaderByte(payload[0]) { + return nil, daprovider.ErrNoBlobReader } } } @@ -137,7 +122,7 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash // It's not safe to trust any part of the payload from this point onwards. // Stage 2: If enabled, decode the zero heavy payload (saves gas based on calldata charging). - if len(payload) > 0 && IsZeroheavyEncodedHeaderByte(payload[0]) { + if len(payload) > 0 && daprovider.IsZeroheavyEncodedHeaderByte(payload[0]) { pl, err := io.ReadAll(io.LimitReader(zeroheavy.NewZeroheavyDecoder(bytes.NewReader(payload[1:])), int64(maxZeroheavyDecompressedLen))) if err != nil { log.Warn("error reading from zeroheavy decoder", err.Error()) @@ -147,7 +132,7 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash } // Stage 3: Decompress the brotli payload and fill the parsedMsg.segments list. - if len(payload) > 0 && IsBrotliMessageHeaderByte(payload[0]) { + if len(payload) > 0 && daprovider.IsBrotliMessageHeaderByte(payload[0]) { decompressed, err := arbcompress.Decompress(payload[1:], MaxDecompressedLen) if err == nil { reader := bytes.NewReader(decompressed) @@ -183,253 +168,24 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash return parsedMsg, nil } -func RecoverPayloadFromDasBatch( - ctx context.Context, - batchNum uint64, - sequencerMsg []byte, - dasReader DataAvailabilityReader, - preimages map[arbutil.PreimageType]map[common.Hash][]byte, - keysetValidationMode KeysetValidationMode, -) ([]byte, error) { - var keccakPreimages map[common.Hash][]byte - if preimages != nil { - if preimages[arbutil.Keccak256PreimageType] == nil { - preimages[arbutil.Keccak256PreimageType] = make(map[common.Hash][]byte) - } - keccakPreimages = preimages[arbutil.Keccak256PreimageType] - } - cert, err := DeserializeDASCertFrom(bytes.NewReader(sequencerMsg[40:])) - if err != nil { - log.Error("Failed to deserialize DAS message", "err", err) - return nil, nil - } - version := cert.Version - recordPreimage := func(key common.Hash, value []byte) { - keccakPreimages[key] = value - } - - if version >= 2 { - log.Error("Your node software is probably out of date", "certificateVersion", version) - return nil, nil - } - - getByHash := func(ctx context.Context, hash common.Hash) ([]byte, error) { - newHash := hash - if version == 0 { - newHash = dastree.FlatHashToTreeHash(hash) - } - - preimage, err := dasReader.GetByHash(ctx, newHash) - if err != nil && hash != newHash { - log.Debug("error fetching new style hash, trying old", "new", newHash, "old", hash, "err", err) - preimage, err = dasReader.GetByHash(ctx, hash) - } - if err != nil { - return nil, err - } - - switch { - case version == 0 && crypto.Keccak256Hash(preimage) != hash: - fallthrough - case version == 1 && dastree.Hash(preimage) != hash: - log.Error( - "preimage mismatch for hash", - "hash", hash, "err", ErrHashMismatch, "version", version, - ) - return nil, ErrHashMismatch - } - return preimage, nil - } - - keysetPreimage, err := getByHash(ctx, cert.KeysetHash) - if err != nil { - log.Error("Couldn't get keyset", "err", err) - return nil, err - } - if keccakPreimages != nil { - dastree.RecordHash(recordPreimage, keysetPreimage) - } - - keyset, err := DeserializeKeyset(bytes.NewReader(keysetPreimage), keysetValidationMode == KeysetDontValidate) - if err != nil { - logLevel := log.Error - if keysetValidationMode == KeysetPanicIfInvalid { - logLevel = log.Crit - } - logLevel("Couldn't deserialize keyset", "err", err, "keysetHash", cert.KeysetHash, "batchNum", batchNum) - return nil, nil - } - err = keyset.VerifySignature(cert.SignersMask, cert.SerializeSignableFields(), cert.Sig) - if err != nil { - log.Error("Bad signature on DAS batch", "err", err) - return nil, nil - } - - maxTimestamp := binary.BigEndian.Uint64(sequencerMsg[8:16]) - if cert.Timeout < maxTimestamp+MinLifetimeSecondsForDataAvailabilityCert { - log.Error("Data availability cert expires too soon", "err", "") - return nil, nil - } - - dataHash := cert.DataHash - payload, err := getByHash(ctx, dataHash) - if err != nil { - log.Error("Couldn't fetch DAS batch contents", "err", err) - return nil, err - } - - if keccakPreimages != nil { - if version == 0 { - treeLeaf := dastree.FlatHashToTreeLeaf(dataHash) - keccakPreimages[dataHash] = payload - keccakPreimages[crypto.Keccak256Hash(treeLeaf)] = treeLeaf - } else { - dastree.RecordHash(recordPreimage, payload) - } - } - - return payload, nil -} - -type DataAvailabilityProvider interface { - // IsValidHeaderByte returns true if the given headerByte has bits corresponding to the DA provider - IsValidHeaderByte(headerByte byte) bool - - // RecoverPayloadFromBatch fetches the underlying payload from the DA provider given the batch header information - RecoverPayloadFromBatch( - ctx context.Context, - batchNum uint64, - batchBlockHash common.Hash, - sequencerMsg []byte, - preimages map[arbutil.PreimageType]map[common.Hash][]byte, - keysetValidationMode KeysetValidationMode, - ) ([]byte, error) -} - -// NewDAProviderDAS is generally meant to be only used by nitro. -// DA Providers should implement methods in the DataAvailabilityProvider interface independently -func NewDAProviderDAS(das DataAvailabilityReader) *dAProviderForDAS { - return &dAProviderForDAS{ - das: das, - } -} - -type dAProviderForDAS struct { - das DataAvailabilityReader -} - -func (d *dAProviderForDAS) IsValidHeaderByte(headerByte byte) bool { - return IsDASMessageHeaderByte(headerByte) -} - -func (d *dAProviderForDAS) RecoverPayloadFromBatch( - ctx context.Context, - batchNum uint64, - batchBlockHash common.Hash, - sequencerMsg []byte, - preimages map[arbutil.PreimageType]map[common.Hash][]byte, - keysetValidationMode KeysetValidationMode, -) ([]byte, error) { - return RecoverPayloadFromDasBatch(ctx, batchNum, sequencerMsg, d.das, preimages, keysetValidationMode) -} - -// NewDAProviderBlobReader is generally meant to be only used by nitro. -// DA Providers should implement methods in the DataAvailabilityProvider interface independently -func NewDAProviderBlobReader(blobReader BlobReader) *dAProviderForBlobReader { - return &dAProviderForBlobReader{ - blobReader: blobReader, - } -} - -type dAProviderForBlobReader struct { - blobReader BlobReader -} - -func (b *dAProviderForBlobReader) IsValidHeaderByte(headerByte byte) bool { - return IsBlobHashesHeaderByte(headerByte) -} - -func (b *dAProviderForBlobReader) RecoverPayloadFromBatch( - ctx context.Context, - batchNum uint64, - batchBlockHash common.Hash, - sequencerMsg []byte, - preimages map[arbutil.PreimageType]map[common.Hash][]byte, - keysetValidationMode KeysetValidationMode, -) ([]byte, error) { - blobHashes := sequencerMsg[41:] - if len(blobHashes)%len(common.Hash{}) != 0 { - return nil, ErrInvalidBlobDataFormat - } - versionedHashes := make([]common.Hash, len(blobHashes)/len(common.Hash{})) - for i := 0; i*32 < len(blobHashes); i += 1 { - copy(versionedHashes[i][:], blobHashes[i*32:(i+1)*32]) - } - kzgBlobs, err := b.blobReader.GetBlobs(ctx, batchBlockHash, versionedHashes) - if err != nil { - return nil, fmt.Errorf("failed to get blobs: %w", err) - } - payload, err := blobs.DecodeBlobs(kzgBlobs) - if err != nil { - log.Warn("Failed to decode blobs", "batchBlockHash", batchBlockHash, "versionedHashes", versionedHashes, "err", err) - return nil, nil - } - return payload, nil -} - -// NewDAProviderEigenDA is generally meant to be only used by nitro. -// DA Providers should implement methods in the DataAvailabilityProvider interface independently -func NewDAProviderEigenDA(eigenDAReader eigenda.EigenDAReader) *daProviderForEigenDA { - return &daProviderForEigenDA{ - eigenDAReader: eigenDAReader, - } -} - -type daProviderForEigenDA struct { - eigenDAReader eigenda.EigenDAReader -} - -func (e *daProviderForEigenDA) IsValidHeaderByte(headerByte byte) bool { - return eigenda.IsEigenDAMessageHeaderByte(headerByte) -} - -func (e *daProviderForEigenDA) RecoverPayloadFromBatch( - ctx context.Context, - batchNum uint64, - batchBlockHash common.Hash, - sequencerMsg []byte, - preimages map[arbutil.PreimageType]map[common.Hash][]byte, - keysetValidationMode KeysetValidationMode, -) ([]byte, error) { - // we start from the 41st byte of sequencerMsg because bytes 0 - 40 are the header, and 40 - 41 is the eigenDA header flag - // we use the binary domain here because this is what we use in the derivation pipeline - return eigenda.RecoverPayloadFromEigenDABatch(ctx, sequencerMsg[41:], e.eigenDAReader, preimages, "binary") -} - -type KeysetValidationMode uint8 - -const KeysetValidate KeysetValidationMode = 0 -const KeysetPanicIfInvalid KeysetValidationMode = 1 -const KeysetDontValidate KeysetValidationMode = 2 - type inboxMultiplexer struct { backend InboxBackend delayedMessagesRead uint64 - daProviders []DataAvailabilityProvider + dapReaders []daprovider.Reader cachedSequencerMessage *sequencerMessage cachedSequencerMessageNum uint64 cachedSegmentNum uint64 cachedSegmentTimestamp uint64 cachedSegmentBlockNumber uint64 cachedSubMessageNumber uint64 - keysetValidationMode KeysetValidationMode + keysetValidationMode daprovider.KeysetValidationMode } -func NewInboxMultiplexer(backend InboxBackend, delayedMessagesRead uint64, daProviders []DataAvailabilityProvider, keysetValidationMode KeysetValidationMode) arbostypes.InboxMultiplexer { +func NewInboxMultiplexer(backend InboxBackend, delayedMessagesRead uint64, dapReaders []daprovider.Reader, keysetValidationMode daprovider.KeysetValidationMode) arbostypes.InboxMultiplexer { return &inboxMultiplexer{ backend: backend, delayedMessagesRead: delayedMessagesRead, - daProviders: daProviders, + dapReaders: dapReaders, keysetValidationMode: keysetValidationMode, } } @@ -452,7 +208,7 @@ func (r *inboxMultiplexer) Pop(ctx context.Context) (*arbostypes.MessageWithMeta } r.cachedSequencerMessageNum = r.backend.GetSequencerInboxPosition() var err error - r.cachedSequencerMessage, err = parseSequencerMessage(ctx, r.cachedSequencerMessageNum, batchBlockHash, bytes, r.daProviders, r.keysetValidationMode) + r.cachedSequencerMessage, err = parseSequencerMessage(ctx, r.cachedSequencerMessageNum, batchBlockHash, bytes, r.dapReaders, r.keysetValidationMode) if err != nil { return nil, err } diff --git a/arbstate/inbox_fuzz_test.go b/arbstate/inbox_fuzz_test.go index b34c02534..5ede32181 100644 --- a/arbstate/inbox_fuzz_test.go +++ b/arbstate/inbox_fuzz_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) type multiplexerBackend struct { @@ -67,7 +68,7 @@ func FuzzInboxMultiplexer(f *testing.F) { delayedMessage: delayedMsg, positionWithinMessage: 0, } - multiplexer := NewInboxMultiplexer(backend, 0, nil, KeysetValidate) + multiplexer := NewInboxMultiplexer(backend, 0, nil, daprovider.KeysetValidate) _, err := multiplexer.Pop(context.TODO()) if err != nil { panic(err) diff --git a/arbutil/correspondingl1blocknumber.go b/arbutil/correspondingl1blocknumber.go index 136eb8e4c..05323ed18 100644 --- a/arbutil/correspondingl1blocknumber.go +++ b/arbutil/correspondingl1blocknumber.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbutil diff --git a/arbutil/format.go b/arbutil/format.go new file mode 100644 index 000000000..041866e4c --- /dev/null +++ b/arbutil/format.go @@ -0,0 +1,19 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbutil + +import ( + "encoding/hex" + "unicode/utf8" +) + +func ToStringOrHex(input []byte) string { + if input == nil { + return "" + } + if utf8.Valid(input) { + return string(input) + } + return hex.EncodeToString(input) +} diff --git a/arbutil/unsafe.go b/arbutil/unsafe.go new file mode 100644 index 000000000..341aa12c1 --- /dev/null +++ b/arbutil/unsafe.go @@ -0,0 +1,28 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbutil + +import "unsafe" + +func SliceToPointer[T any](slice []T) *T { + if len(slice) == 0 { + return nil + } + return &slice[0] +} + +func SliceToUnsafePointer[T any](slice []T) unsafe.Pointer { + return unsafe.Pointer(SliceToPointer(slice)) +} + +// does a defensive copy due to Go's lake of immutable types +func PointerToSlice[T any](pointer *T, length int) []T { + return CopySlice(unsafe.Slice(pointer, length)) +} + +func CopySlice[T any](slice []T) []T { + output := make([]T, len(slice)) + copy(output, slice) + return output +} diff --git a/arbutil/wait_for_l1.go b/arbutil/wait_for_l1.go index cfe24cf63..eaa5d0790 100644 --- a/arbutil/wait_for_l1.go +++ b/arbutil/wait_for_l1.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbutil @@ -41,7 +41,6 @@ func SendTxAsCall(ctx context.Context, client L1Interface, tx *types.Transaction From: from, To: tx.To(), Gas: gas, - GasPrice: tx.GasPrice(), GasFeeCap: tx.GasFeeCap(), GasTipCap: tx.GasTipCap(), Value: tx.Value(), diff --git a/blocks_reexecutor/blocks_reexecutor.go b/blocks_reexecutor/blocks_reexecutor.go index bb6de00ca..1e4a06fe9 100644 --- a/blocks_reexecutor/blocks_reexecutor.go +++ b/blocks_reexecutor/blocks_reexecutor.go @@ -35,17 +35,16 @@ func (c *Config) Validate() error { if c.EndBlock < c.StartBlock { return errors.New("invalid block range for blocks re-execution") } - if c.Room == 0 { - return errors.New("room for blocks re-execution cannot be zero") + if c.Room <= 0 { + return errors.New("room for blocks re-execution should be greater than 0") } return nil } var DefaultConfig = Config{ - Enable: false, - Mode: "random", - Room: runtime.NumCPU(), - BlocksPerThread: 10000, + Enable: false, + Mode: "random", + Room: runtime.NumCPU(), } var TestConfig = Config{ @@ -66,13 +65,14 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { type BlocksReExecutor struct { stopwaiter.StopWaiter - config *Config - blockchain *core.BlockChain - stateFor arbitrum.StateForHeaderFunction - done chan struct{} - fatalErrChan chan error - startBlock uint64 - currentBlock uint64 + config *Config + blockchain *core.BlockChain + stateFor arbitrum.StateForHeaderFunction + done chan struct{} + fatalErrChan chan error + startBlock uint64 + currentBlock uint64 + blocksPerThread uint64 } func New(c *Config, blockchain *core.BlockChain, fatalErrChan chan error) *BlocksReExecutor { @@ -84,32 +84,47 @@ func New(c *Config, blockchain *core.BlockChain, fatalErrChan chan error) *Block start = chainStart end = chainEnd } - if start < chainStart { - log.Warn("state reexecutor's start block number is lower than genesis, resetting to genesis") + if start < chainStart || start > chainEnd { + log.Warn("invalid state reexecutor's start block number, resetting to genesis", "start", start, "genesis", chainStart) start = chainStart } - if end > chainEnd { - log.Warn("state reexecutor's end block number is greater than latest, resetting to latest") + if end > chainEnd || end < chainStart { + log.Warn("invalid state reexecutor's end block number, resetting to latest", "end", end, "latest", chainEnd) end = chainEnd } + blocksPerThread := uint64(10000) + if c.BlocksPerThread != 0 { + blocksPerThread = c.BlocksPerThread + } if c.Mode == "random" && end != start { - if c.BlocksPerThread > end-start { - c.BlocksPerThread = end - start + // Reexecute a range of 10000 or (non-zero) c.BlocksPerThread number of blocks between start to end picked randomly + rng := blocksPerThread + if rng > end-start { + rng = end - start } - start += uint64(rand.Intn(int(end - start - c.BlocksPerThread + 1))) - end = start + c.BlocksPerThread + start += uint64(rand.Intn(int(end - start - rng + 1))) + end = start + rng } - // inclusive of block reexecution [start, end] - if start > 0 { + // Inclusive of block reexecution [start, end] + // Do not reexecute genesis block i,e chainStart + if start > 0 && start != chainStart { start-- } + // Divide work equally among available threads when BlocksPerThread is zero + if c.BlocksPerThread == 0 { + work := (end - start) / uint64(c.Room) + if work > 0 { + blocksPerThread = work + } + } return &BlocksReExecutor{ - config: c, - blockchain: blockchain, - currentBlock: end, - startBlock: start, - done: make(chan struct{}, c.Room), - fatalErrChan: fatalErrChan, + config: c, + blockchain: blockchain, + currentBlock: end, + startBlock: start, + blocksPerThread: blocksPerThread, + done: make(chan struct{}, c.Room), + fatalErrChan: fatalErrChan, stateFor: func(header *types.Header) (*state.StateDB, arbitrum.StateReleaseFunc, error) { state, err := blockchain.StateAt(header.Root) return state, arbitrum.NoopStateRelease, err @@ -119,17 +134,17 @@ func New(c *Config, blockchain *core.BlockChain, fatalErrChan chan error) *Block // LaunchBlocksReExecution launches the thread to apply blocks of range [currentBlock-s.config.BlocksPerThread, currentBlock] to the last available valid state func (s *BlocksReExecutor) LaunchBlocksReExecution(ctx context.Context, currentBlock uint64) uint64 { - start := arbmath.SaturatingUSub(currentBlock, s.config.BlocksPerThread) + start := arbmath.SaturatingUSub(currentBlock, s.blocksPerThread) if start < s.startBlock { start = s.startBlock } - // we don't use state release pattern here - // TODO do we want to use release pattern here? - startState, startHeader, _, err := arbitrum.FindLastAvailableState(ctx, s.blockchain, s.stateFor, s.blockchain.GetHeaderByNumber(start), nil, -1) + startState, startHeader, release, err := arbitrum.FindLastAvailableState(ctx, s.blockchain, s.stateFor, s.blockchain.GetHeaderByNumber(start), nil, -1) if err != nil { s.fatalErrChan <- fmt.Errorf("blocksReExecutor failed to get last available state while searching for state at %d, err: %w", start, err) return s.startBlock } + // NoOp + defer release() start = startHeader.Number.Uint64() s.LaunchThread(func(ctx context.Context) { _, err := arbitrum.AdvanceStateUpToBlock(ctx, s.blockchain, startState, s.blockchain.GetHeaderByNumber(currentBlock), startHeader, nil) @@ -169,9 +184,14 @@ func (s *BlocksReExecutor) Impl(ctx context.Context) { log.Info("BlocksReExecutor successfully completed re-execution of blocks against historic state", "stateAt", s.startBlock, "startBlock", s.startBlock+1, "endBlock", end) } -func (s *BlocksReExecutor) Start(ctx context.Context) { +func (s *BlocksReExecutor) Start(ctx context.Context, done chan struct{}) { s.StopWaiter.Start(ctx, s) - s.LaunchThread(s.Impl) + s.LaunchThread(func(ctx context.Context) { + s.Impl(ctx) + if done != nil { + close(done) + } + }) } func (s *BlocksReExecutor) StopAndWait() { diff --git a/broadcastclient/broadcastclient_test.go b/broadcastclient/broadcastclient_test.go index 84356d77e..44b48192a 100644 --- a/broadcastclient/broadcastclient_test.go +++ b/broadcastclient/broadcastclient_test.go @@ -105,7 +105,7 @@ func testReceiveMessages(t *testing.T, clientCompression bool, serverCompression go func() { for i := 0; i < messageCount; i++ { - Require(t, b.BroadcastSingle(arbostypes.TestMessageWithMetadataAndRequestId, arbutil.MessageIndex(i))) + Require(t, b.BroadcastSingle(arbostypes.TestMessageWithMetadataAndRequestId, arbutil.MessageIndex(i), nil)) } }() @@ -156,7 +156,7 @@ func TestInvalidSignature(t *testing.T) { go func() { for i := 0; i < messageCount; i++ { - Require(t, b.BroadcastSingle(arbostypes.TestMessageWithMetadataAndRequestId, arbutil.MessageIndex(i))) + Require(t, b.BroadcastSingle(arbostypes.TestMessageWithMetadataAndRequestId, arbutil.MessageIndex(i), nil)) } }() @@ -316,7 +316,7 @@ func TestServerClientDisconnect(t *testing.T) { broadcastClient.Start(ctx) t.Log("broadcasting seq 0 message") - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 0)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 0, nil)) // Wait for client to receive batch to ensure it is connected timer := time.NewTimer(5 * time.Second) @@ -387,7 +387,7 @@ func TestBroadcastClientConfirmedMessage(t *testing.T) { broadcastClient.Start(ctx) t.Log("broadcasting seq 0 message") - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 0)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 0, nil)) // Wait for client to receive batch to ensure it is connected timer := time.NewTimer(5 * time.Second) @@ -724,8 +724,8 @@ func TestBroadcasterSendsCachedMessagesOnClientConnect(t *testing.T) { Require(t, b.Start(ctx)) defer b.StopAndWait() - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 0)) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 1)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 0, nil)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 1, nil)) var wg sync.WaitGroup for i := 0; i < 2; i++ { diff --git a/broadcaster/broadcaster.go b/broadcaster/broadcaster.go index 242b8f9ee..ba95f2d8a 100644 --- a/broadcaster/broadcaster.go +++ b/broadcaster/broadcaster.go @@ -11,6 +11,7 @@ import ( "github.com/gobwas/ws" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbos/arbostypes" @@ -38,7 +39,11 @@ func NewBroadcaster(config wsbroadcastserver.BroadcasterConfigFetcher, chainId u } } -func (b *Broadcaster) NewBroadcastFeedMessage(message arbostypes.MessageWithMetadata, sequenceNumber arbutil.MessageIndex) (*m.BroadcastFeedMessage, error) { +func (b *Broadcaster) NewBroadcastFeedMessage( + message arbostypes.MessageWithMetadata, + sequenceNumber arbutil.MessageIndex, + blockHash *common.Hash, +) (*m.BroadcastFeedMessage, error) { var messageSignature []byte if b.dataSigner != nil { hash, err := message.Hash(sequenceNumber, b.chainId) @@ -54,18 +59,23 @@ func (b *Broadcaster) NewBroadcastFeedMessage(message arbostypes.MessageWithMeta return &m.BroadcastFeedMessage{ SequenceNumber: sequenceNumber, Message: message, + BlockHash: blockHash, Signature: messageSignature, }, nil } -func (b *Broadcaster) BroadcastSingle(msg arbostypes.MessageWithMetadata, seq arbutil.MessageIndex) (err error) { +func (b *Broadcaster) BroadcastSingle( + msg arbostypes.MessageWithMetadata, + seq arbutil.MessageIndex, + blockHash *common.Hash, +) (err error) { defer func() { if r := recover(); r != nil { log.Error("recovered error in BroadcastSingle", "recover", r, "backtrace", string(debug.Stack())) err = errors.New("panic in BroadcastSingle") } }() - bfm, err := b.NewBroadcastFeedMessage(msg, seq) + bfm, err := b.NewBroadcastFeedMessage(msg, seq, blockHash) if err != nil { return err } @@ -82,7 +92,10 @@ func (b *Broadcaster) BroadcastSingleFeedMessage(bfm *m.BroadcastFeedMessage) { b.BroadcastFeedMessages(broadcastFeedMessages) } -func (b *Broadcaster) BroadcastMessages(messages []arbostypes.MessageWithMetadata, seq arbutil.MessageIndex) (err error) { +func (b *Broadcaster) BroadcastMessages( + messagesWithBlockHash []arbostypes.MessageWithMetadataAndBlockHash, + seq arbutil.MessageIndex, +) (err error) { defer func() { if r := recover(); r != nil { log.Error("recovered error in BroadcastMessages", "recover", r, "backtrace", string(debug.Stack())) @@ -90,8 +103,8 @@ func (b *Broadcaster) BroadcastMessages(messages []arbostypes.MessageWithMetadat } }() var feedMessages []*m.BroadcastFeedMessage - for i, msg := range messages { - bfm, err := b.NewBroadcastFeedMessage(msg, seq+arbutil.MessageIndex(i)) + for i, msg := range messagesWithBlockHash { + bfm, err := b.NewBroadcastFeedMessage(msg.MessageWithMeta, seq+arbutil.MessageIndex(i), msg.BlockHash) if err != nil { return err } diff --git a/broadcaster/broadcaster_test.go b/broadcaster/broadcaster_test.go index 8ac06e970..dc208f416 100644 --- a/broadcaster/broadcaster_test.go +++ b/broadcaster/broadcaster_test.go @@ -70,17 +70,17 @@ func TestBroadcasterMessagesRemovedOnConfirmation(t *testing.T) { } // Normal broadcasting and confirming - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 1)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 1, nil)) waitUntilUpdated(t, expectMessageCount(1, "after 1 message")) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 2)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 2, nil)) waitUntilUpdated(t, expectMessageCount(2, "after 2 messages")) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 3)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 3, nil)) waitUntilUpdated(t, expectMessageCount(3, "after 3 messages")) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 4)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 4, nil)) waitUntilUpdated(t, expectMessageCount(4, "after 4 messages")) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 5)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 5, nil)) waitUntilUpdated(t, expectMessageCount(5, "after 4 messages")) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 6)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 6, nil)) waitUntilUpdated(t, expectMessageCount(6, "after 4 messages")) b.Confirm(4) @@ -96,7 +96,7 @@ func TestBroadcasterMessagesRemovedOnConfirmation(t *testing.T) { "nothing changed because confirmed sequence number before cache")) b.Confirm(5) - Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 7)) + Require(t, b.BroadcastSingle(arbostypes.EmptyTestMessageWithMetadata, 7, nil)) waitUntilUpdated(t, expectMessageCount(2, "after 7 messages, 5 cleared by confirm")) diff --git a/broadcaster/message/message.go b/broadcaster/message/message.go index a575ae5cd..aca959875 100644 --- a/broadcaster/message/message.go +++ b/broadcaster/message/message.go @@ -34,6 +34,7 @@ type BroadcastMessage struct { type BroadcastFeedMessage struct { SequenceNumber arbutil.MessageIndex `json:"sequenceNumber"` Message arbostypes.MessageWithMetadata `json:"message"` + BlockHash *common.Hash `json:"blockHash,omitempty"` Signature []byte `json:"signature"` CumulativeSumMsgSize uint64 `json:"-"` diff --git a/broadcaster/message/message_serialization_test.go b/broadcaster/message/message_serialization_test.go index c3e14a86a..1d8c10e38 100644 --- a/broadcaster/message/message_serialization_test.go +++ b/broadcaster/message/message_serialization_test.go @@ -13,7 +13,40 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" ) -func ExampleBroadcastMessage_broadcastfeedmessage() { +func ExampleBroadcastMessage_broadcastfeedmessageWithBlockHash() { + var requestId common.Hash + msg := BroadcastMessage{ + Version: 1, + Messages: []*BroadcastFeedMessage{ + { + SequenceNumber: 12345, + Message: arbostypes.MessageWithMetadata{ + Message: &arbostypes.L1IncomingMessage{ + Header: &arbostypes.L1IncomingMessageHeader{ + Kind: 0, + Poster: [20]byte{}, + BlockNumber: 0, + Timestamp: 0, + RequestId: &requestId, + L1BaseFee: big.NewInt(0), + }, + L2msg: []byte{0xde, 0xad, 0xbe, 0xef}, + }, + DelayedMessagesRead: 3333, + }, + BlockHash: &common.Hash{0: 0xff}, + Signature: nil, + }, + }, + } + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + _ = encoder.Encode(msg) + fmt.Println(buf.String()) + // Output: {"version":1,"messages":[{"sequenceNumber":12345,"message":{"message":{"header":{"kind":0,"sender":"0x0000000000000000000000000000000000000000","blockNumber":0,"timestamp":0,"requestId":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeeL1":0},"l2Msg":"3q2+7w=="},"delayedMessagesRead":3333},"blockHash":"0xff00000000000000000000000000000000000000000000000000000000000000","signature":null}]} +} + +func ExampleBroadcastMessage_broadcastfeedmessageWithoutBlockHash() { var requestId common.Hash msg := BroadcastMessage{ Version: 1, diff --git a/cmd/chaininfo/arbitrum_chain_info.json b/cmd/chaininfo/arbitrum_chain_info.json index 31d25cfdf..7d47d13e8 100644 --- a/cmd/chaininfo/arbitrum_chain_info.json +++ b/cmd/chaininfo/arbitrum_chain_info.json @@ -247,5 +247,54 @@ "validator-wallet-creator": "0x894fC71fA0A666352824EC954B401573C861D664", "deployed-at": 4139226 } + }, + { + "chain-id": 23011913, + "parent-chain-id": 421614, + "chain-name": "stylus-testnet", + "sequencer-url": "https://stylus-testnet-sequencer.arbitrum.io/rpc", + "feed-url": "wss://stylus-testnet.arbitrum.io/feed", + "chain-config": + { + "chainId": 23011913, + "homesteadBlock": 0, + "daoForkBlock": null, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "clique": + { + "period": 0, + "epoch": 0 + }, + "arbitrum": + { + "EnableArbOS": true, + "AllowDebugPrecompiles": false, + "DataAvailabilityCommittee": false, + "InitialArbOSVersion": 10, + "InitialChainOwner": "0x35c8d15334Eaf0e4b82417Fe10e28deEa0c5C32B", + "GenesisBlockNum": 0 + } + }, + "rollup": + { + "bridge": "0x35aa95ac4747D928E2Cd42FE4461F6D9d1826346", + "inbox": "0xe1e3b1CBaCC870cb6e5F4Bdf246feB6eB5cD351B", + "sequencer-inbox": "0x00A0F15b79d1D3e5991929FaAbCF2AA65623530c", + "rollup": "0x94db9E36d9336cD6F9FfcAd399dDa6Cc05299898", + "validator-utils": "0x8aB661AAC7693F60DF34464B6f964d3C3977e2D3", + "validator-wallet-creator": "0x6065949AC7D6e86Ce9EAC2089C6b68B0b7077ED6", + "deployed-at": 1847 + } } ] \ No newline at end of file diff --git a/cmd/conf/chain.go b/cmd/conf/chain.go index 531945b4d..b85f7727b 100644 --- a/cmd/conf/chain.go +++ b/cmd/conf/chain.go @@ -15,22 +15,21 @@ import ( type ParentChainConfig struct { ID uint64 `koanf:"id"` Connection rpcclient.ClientConfig `koanf:"connection" reload:"hot"` - Wallet genericconf.WalletConfig `koanf:"wallet"` BlobClient headerreader.BlobClientConfig `koanf:"blob-client"` } var L1ConnectionConfigDefault = rpcclient.ClientConfig{ - URL: "", - Retries: 2, - Timeout: time.Minute, - ConnectionWait: time.Minute, - ArgLogLimit: 2048, + URL: "", + Retries: 2, + Timeout: time.Minute, + ConnectionWait: time.Minute, + ArgLogLimit: 2048, + WebsocketMessageSizeLimit: 256 * 1024 * 1024, } var L1ConfigDefault = ParentChainConfig{ ID: 0, Connection: L1ConnectionConfigDefault, - Wallet: DefaultL1WalletConfig, BlobClient: headerreader.DefaultBlobClientConfig, } @@ -45,14 +44,9 @@ var DefaultL1WalletConfig = genericconf.WalletConfig{ func L1ConfigAddOptions(prefix string, f *flag.FlagSet) { f.Uint64(prefix+".id", L1ConfigDefault.ID, "if set other than 0, will be used to validate database and L1 connection") rpcclient.RPCClientAddOptions(prefix+".connection", f, &L1ConfigDefault.Connection) - genericconf.WalletConfigAddOptions(prefix+".wallet", f, L1ConfigDefault.Wallet.Pathname) headerreader.BlobClientAddOptions(prefix+".blob-client", f) } -func (c *ParentChainConfig) ResolveDirectoryNames(chain string) { - c.Wallet.ResolveDirectoryNames(chain) -} - func (c *ParentChainConfig) Validate() error { return c.Connection.Validate() } diff --git a/cmd/conf/database.go b/cmd/conf/database.go index b049375d6..6fde00579 100644 --- a/cmd/conf/database.go +++ b/cmd/conf/database.go @@ -5,20 +5,25 @@ package conf import ( "fmt" + "math" "os" "path" "path/filepath" + "runtime" + "time" + "github.com/ethereum/go-ethereum/ethdb/pebble" flag "github.com/spf13/pflag" ) type PersistentConfig struct { - GlobalConfig string `koanf:"global-config"` - Chain string `koanf:"chain"` - LogDir string `koanf:"log-dir"` - Handles int `koanf:"handles"` - Ancient string `koanf:"ancient"` - DBEngine string `koanf:"db-engine"` + GlobalConfig string `koanf:"global-config"` + Chain string `koanf:"chain"` + LogDir string `koanf:"log-dir"` + Handles int `koanf:"handles"` + Ancient string `koanf:"ancient"` + DBEngine string `koanf:"db-engine"` + Pebble PebbleConfig `koanf:"pebble"` } var PersistentConfigDefault = PersistentConfig{ @@ -28,6 +33,7 @@ var PersistentConfigDefault = PersistentConfig{ Handles: 512, Ancient: "", DBEngine: "leveldb", + Pebble: PebbleConfigDefault, } func PersistentConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -37,6 +43,7 @@ func PersistentConfigAddOptions(prefix string, f *flag.FlagSet) { f.Int(prefix+".handles", PersistentConfigDefault.Handles, "number of file descriptor handles to use for the database") f.String(prefix+".ancient", PersistentConfigDefault.Ancient, "directory of ancient where the chain freezer can be opened") f.String(prefix+".db-engine", PersistentConfigDefault.DBEngine, "backing database implementation to use ('leveldb' or 'pebble')") + PebbleConfigAddOptions(prefix+".pebble", f) } func (c *PersistentConfig) ResolveDirectoryNames() error { @@ -94,5 +101,174 @@ func (c *PersistentConfig) Validate() error { if c.DBEngine != "leveldb" && c.DBEngine != "pebble" { return fmt.Errorf(`invalid .db-engine choice: %q, allowed "leveldb" or "pebble"`, c.DBEngine) } + if c.DBEngine == "pebble" { + if err := c.Pebble.Validate(); err != nil { + return err + } + } return nil } + +type PebbleConfig struct { + MaxConcurrentCompactions int `koanf:"max-concurrent-compactions"` + Experimental PebbleExperimentalConfig `koanf:"experimental"` +} + +var PebbleConfigDefault = PebbleConfig{ + MaxConcurrentCompactions: runtime.NumCPU(), + Experimental: PebbleExperimentalConfigDefault, +} + +func PebbleConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Int(prefix+".max-concurrent-compactions", PebbleConfigDefault.MaxConcurrentCompactions, "maximum number of concurrent compactions") + PebbleExperimentalConfigAddOptions(prefix+".experimental", f) +} + +func (c *PebbleConfig) Validate() error { + if c.MaxConcurrentCompactions < 1 { + return fmt.Errorf("invalid .max-concurrent-compactions value: %d, has to be greater then 0", c.MaxConcurrentCompactions) + } + if err := c.Experimental.Validate(); err != nil { + return err + } + return nil +} + +type PebbleExperimentalConfig struct { + BytesPerSync int `koanf:"bytes-per-sync"` + L0CompactionFileThreshold int `koanf:"l0-compaction-file-threshold"` + L0CompactionThreshold int `koanf:"l0-compaction-threshold"` + L0StopWritesThreshold int `koanf:"l0-stop-writes-threshold"` + LBaseMaxBytes int64 `koanf:"l-base-max-bytes"` + MemTableStopWritesThreshold int `koanf:"mem-table-stop-writes-threshold"` + DisableAutomaticCompactions bool `koanf:"disable-automatic-compactions"` + WALBytesPerSync int `koanf:"wal-bytes-per-sync"` + WALDir string `koanf:"wal-dir"` + WALMinSyncInterval int `koanf:"wal-min-sync-interval"` + TargetByteDeletionRate int `koanf:"target-byte-deletion-rate"` + + // level specific + BlockSize int `koanf:"block-size"` + IndexBlockSize int `koanf:"index-block-size"` + TargetFileSize int64 `koanf:"target-file-size"` + TargetFileSizeEqualLevels bool `koanf:"target-file-size-equal-levels"` + + // pebble experimental + L0CompactionConcurrency int `koanf:"l0-compaction-concurrency"` + CompactionDebtConcurrency uint64 `koanf:"compaction-debt-concurrency"` + ReadCompactionRate int64 `koanf:"read-compaction-rate"` + ReadSamplingMultiplier int64 `koanf:"read-sampling-multiplier"` + MaxWriterConcurrency int `koanf:"max-writer-concurrency"` + ForceWriterParallelism bool `koanf:"force-writer-parallelism"` +} + +var PebbleExperimentalConfigDefault = PebbleExperimentalConfig{ + BytesPerSync: 512 << 10, // 512 KB + L0CompactionFileThreshold: 500, + L0CompactionThreshold: 4, + L0StopWritesThreshold: 12, + LBaseMaxBytes: 64 << 20, // 64 MB + MemTableStopWritesThreshold: 2, + DisableAutomaticCompactions: false, + WALBytesPerSync: 0, // no background syncing + WALDir: "", // use same dir as for sstables + WALMinSyncInterval: 0, // no artificial delay + TargetByteDeletionRate: 0, // deletion pacing disabled + + BlockSize: 4 << 10, // 4 KB + IndexBlockSize: 4 << 10, // 4 KB + TargetFileSize: 2 << 20, // 2 MB + TargetFileSizeEqualLevels: true, + + L0CompactionConcurrency: 10, + CompactionDebtConcurrency: 1 << 30, // 1GB + ReadCompactionRate: 16000, // see ReadSamplingMultiplier comment + ReadSamplingMultiplier: -1, // geth default, disables read sampling and disables read triggered compaction + MaxWriterConcurrency: 0, + ForceWriterParallelism: false, +} + +func PebbleExperimentalConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Int(prefix+".bytes-per-sync", PebbleExperimentalConfigDefault.BytesPerSync, "number of bytes to write to a SSTable before calling Sync on it in the background") + f.Int(prefix+".l0-compaction-file-threshold", PebbleExperimentalConfigDefault.L0CompactionFileThreshold, "count of L0 files necessary to trigger an L0 compaction") + f.Int(prefix+".l0-compaction-threshold", PebbleExperimentalConfigDefault.L0CompactionThreshold, "amount of L0 read-amplification necessary to trigger an L0 compaction") + f.Int(prefix+".l0-stop-writes-threshold", PebbleExperimentalConfigDefault.L0StopWritesThreshold, "hard limit on L0 read-amplification, computed as the number of L0 sublevels. Writes are stopped when this threshold is reached") + f.Int64(prefix+".l-base-max-bytes", PebbleExperimentalConfigDefault.LBaseMaxBytes, "The maximum number of bytes for LBase. The base level is the level which L0 is compacted into. The base level is determined dynamically based on the existing data in the LSM. The maximum number of bytes for other levels is computed dynamically based on the base level's maximum size. When the maximum number of bytes for a level is exceeded, compaction is requested.") + f.Int(prefix+".mem-table-stop-writes-threshold", PebbleExperimentalConfigDefault.MemTableStopWritesThreshold, "hard limit on the number of queued of MemTables") + f.Bool(prefix+".disable-automatic-compactions", PebbleExperimentalConfigDefault.DisableAutomaticCompactions, "disables automatic compactions") + f.Int(prefix+".wal-bytes-per-sync", PebbleExperimentalConfigDefault.WALBytesPerSync, "number of bytes to write to a write-ahead log (WAL) before calling Sync on it in the background") + f.String(prefix+".wal-dir", PebbleExperimentalConfigDefault.WALDir, "absolute path of directory to store write-ahead logs (WALs) in. If empty, WALs will be stored in the same directory as sstables") + f.Int(prefix+".wal-min-sync-interval", PebbleExperimentalConfigDefault.WALMinSyncInterval, "minimum duration in microseconds between syncs of the WAL. If WAL syncs are requested faster than this interval, they will be artificially delayed.") + f.Int(prefix+".target-byte-deletion-rate", PebbleExperimentalConfigDefault.TargetByteDeletionRate, "rate (in bytes per second) at which sstable file deletions are limited to (under normal circumstances).") + f.Int(prefix+".block-size", PebbleExperimentalConfigDefault.BlockSize, "target uncompressed size in bytes of each table block") + f.Int(prefix+".index-block-size", PebbleExperimentalConfigDefault.IndexBlockSize, fmt.Sprintf("target uncompressed size in bytes of each index block. When the index block size is larger than this target, two-level indexes are automatically enabled. Setting this option to a large value (such as %d) disables the automatic creation of two-level indexes.", math.MaxInt32)) + f.Int64(prefix+".target-file-size", PebbleExperimentalConfigDefault.TargetFileSize, "target file size for the level 0") + f.Bool(prefix+".target-file-size-equal-levels", PebbleExperimentalConfigDefault.TargetFileSizeEqualLevels, "if true same target-file-size will be uses for all levels, otherwise target size for layer n = 2 * target size for layer n - 1") + + f.Int(prefix+".l0-compaction-concurrency", PebbleExperimentalConfigDefault.L0CompactionConcurrency, "threshold of L0 read-amplification at which compaction concurrency is enabled (if compaction-debt-concurrency was not already exceeded). Every multiple of this value enables another concurrent compaction up to max-concurrent-compactions.") + f.Uint64(prefix+".compaction-debt-concurrency", PebbleExperimentalConfigDefault.CompactionDebtConcurrency, "controls the threshold of compaction debt at which additional compaction concurrency slots are added. For every multiple of this value in compaction debt bytes, an additional concurrent compaction is added. This works \"on top\" of l0-compaction-concurrency, so the higher of the count of compaction concurrency slots as determined by the two options is chosen.") + f.Int64(prefix+".read-compaction-rate", PebbleExperimentalConfigDefault.ReadCompactionRate, "controls the frequency of read triggered compactions by adjusting `AllowedSeeks` in manifest.FileMetadata: AllowedSeeks = FileSize / ReadCompactionRate") + f.Int64(prefix+".read-sampling-multiplier", PebbleExperimentalConfigDefault.ReadSamplingMultiplier, "a multiplier for the readSamplingPeriod in iterator.maybeSampleRead() to control the frequency of read sampling to trigger a read triggered compaction. A value of -1 prevents sampling and disables read triggered compactions. Geth default is -1. The pebble default is 1 << 4. which gets multiplied with a constant of 1 << 16 to yield 1 << 20 (1MB).") + f.Int(prefix+".max-writer-concurrency", PebbleExperimentalConfigDefault.MaxWriterConcurrency, "maximum number of compression workers the compression queue is allowed to use. If max-writer-concurrency > 0, then the Writer will use parallelism, to compress and write blocks to disk. Otherwise, the writer will compress and write blocks to disk synchronously.") + f.Bool(prefix+".force-writer-parallelism", PebbleExperimentalConfigDefault.ForceWriterParallelism, "force parallelism in the sstable Writer for the metamorphic tests. Even with the MaxWriterConcurrency option set, pebble only enables parallelism in the sstable Writer if there is enough CPU available, and this option bypasses that.") +} + +func (c *PebbleExperimentalConfig) Validate() error { + if c.WALDir != "" && !filepath.IsAbs(c.WALDir) { + return fmt.Errorf("invalid .wal-dir directory (%s) - has to be an absolute path", c.WALDir) + } + // TODO + return nil +} + +func (c *PebbleConfig) ExtraOptions(namespace string) *pebble.ExtraOptions { + var maxConcurrentCompactions func() int + if c.MaxConcurrentCompactions > 0 { + maxConcurrentCompactions = func() int { return c.MaxConcurrentCompactions } + } + var walMinSyncInterval func() time.Duration + if c.Experimental.WALMinSyncInterval > 0 { + walMinSyncInterval = func() time.Duration { + return time.Microsecond * time.Duration(c.Experimental.WALMinSyncInterval) + } + } + var levels []pebble.ExtraLevelOptions + for i := 0; i < 7; i++ { + targetFileSize := c.Experimental.TargetFileSize + if !c.Experimental.TargetFileSizeEqualLevels { + targetFileSize = targetFileSize << i + } + levels = append(levels, pebble.ExtraLevelOptions{ + BlockSize: c.Experimental.BlockSize, + IndexBlockSize: c.Experimental.IndexBlockSize, + TargetFileSize: targetFileSize, + }) + } + walDir := c.Experimental.WALDir + if walDir != "" { + walDir = path.Join(walDir, namespace) + } + return &pebble.ExtraOptions{ + BytesPerSync: c.Experimental.BytesPerSync, + L0CompactionFileThreshold: c.Experimental.L0CompactionFileThreshold, + L0CompactionThreshold: c.Experimental.L0CompactionThreshold, + L0StopWritesThreshold: c.Experimental.L0StopWritesThreshold, + LBaseMaxBytes: c.Experimental.LBaseMaxBytes, + MemTableStopWritesThreshold: c.Experimental.MemTableStopWritesThreshold, + MaxConcurrentCompactions: maxConcurrentCompactions, + DisableAutomaticCompactions: c.Experimental.DisableAutomaticCompactions, + WALBytesPerSync: c.Experimental.WALBytesPerSync, + WALDir: walDir, + WALMinSyncInterval: walMinSyncInterval, + TargetByteDeletionRate: c.Experimental.TargetByteDeletionRate, + Experimental: pebble.ExtraOptionsExperimental{ + L0CompactionConcurrency: c.Experimental.L0CompactionConcurrency, + CompactionDebtConcurrency: c.Experimental.CompactionDebtConcurrency, + ReadCompactionRate: c.Experimental.ReadCompactionRate, + ReadSamplingMultiplier: c.Experimental.ReadSamplingMultiplier, + MaxWriterConcurrency: c.Experimental.MaxWriterConcurrency, + ForceWriterParallelism: c.Experimental.ForceWriterParallelism, + }, + Levels: levels, + } +} diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 8a6c5096f..7c0db0b05 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -1,15 +1,21 @@ package conf import ( + "fmt" + "runtime" + "strings" "time" "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/execution/gethexec" "github.com/spf13/pflag" ) type InitConfig struct { Force bool `koanf:"force"` Url string `koanf:"url"` + Latest string `koanf:"latest"` + LatestBase string `koanf:"latest-base"` DownloadPath string `koanf:"download-path"` DownloadPoll time.Duration `koanf:"download-poll"` DevInit bool `koanf:"dev-init"` @@ -21,13 +27,18 @@ type InitConfig struct { ThenQuit bool `koanf:"then-quit"` Prune string `koanf:"prune"` PruneBloomSize uint64 `koanf:"prune-bloom-size"` + PruneThreads int `koanf:"prune-threads"` + PruneTrieCleanCache int `koanf:"prune-trie-clean-cache"` ResetToMessage int64 `koanf:"reset-to-message"` RecreateMissingStateFrom uint64 `koanf:"recreate-missing-state-from"` + RebuildLocalWasm bool `koanf:"rebuild-local-wasm"` } var InitConfigDefault = InitConfig{ Force: false, Url: "", + Latest: "", + LatestBase: "https://snapshot.arbitrum.foundation/", DownloadPath: "/tmp/", DownloadPoll: time.Minute, DevInit: false, @@ -39,13 +50,18 @@ var InitConfigDefault = InitConfig{ ThenQuit: false, Prune: "", PruneBloomSize: 2048, + PruneThreads: runtime.NumCPU(), + PruneTrieCleanCache: gethexec.DefaultCachingConfig.TrieCleanCache, ResetToMessage: -1, RecreateMissingStateFrom: 0, // 0 = disabled + RebuildLocalWasm: true, } func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".force", InitConfigDefault.Force, "if true: in case database exists init code will be reexecuted and genesis block compared to database") - f.String(prefix+".url", InitConfigDefault.Url, "url to download initializtion data - will poll if download fails") + f.String(prefix+".url", InitConfigDefault.Url, "url to download initialization data - will poll if download fails") + f.String(prefix+".latest", InitConfigDefault.Latest, "if set, searches for the latest snapshot of the given kind "+acceptedSnapshotKindsStr) + f.String(prefix+".latest-base", InitConfigDefault.LatestBase, "base url used when searching for the latest") f.String(prefix+".download-path", InitConfigDefault.DownloadPath, "path to save temp downloaded file") f.Duration(prefix+".download-poll", InitConfigDefault.DownloadPoll, "how long to wait between polling attempts") f.Bool(prefix+".dev-init", InitConfigDefault.DevInit, "init with dev data (1 account with balance) instead of file import") @@ -57,13 +73,39 @@ func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Uint(prefix+".accounts-per-sync", InitConfigDefault.AccountsPerSync, "during init - sync database every X accounts. Lower value for low-memory systems. 0 disables.") f.String(prefix+".prune", InitConfigDefault.Prune, "pruning for a given use: \"full\" for full nodes serving RPC requests, or \"validator\" for validators") f.Uint64(prefix+".prune-bloom-size", InitConfigDefault.PruneBloomSize, "the amount of memory in megabytes to use for the pruning bloom filter (higher values prune better)") + f.Int(prefix+".prune-threads", InitConfigDefault.PruneThreads, "the number of threads to use when pruning") + f.Int(prefix+".prune-trie-clean-cache", InitConfigDefault.PruneTrieCleanCache, "amount of memory in megabytes to cache unchanged state trie nodes with when traversing state database during pruning") f.Int64(prefix+".reset-to-message", InitConfigDefault.ResetToMessage, "forces a reset to an old message height. Also set max-reorg-resequence-depth=0 to force re-reading messages") f.Uint64(prefix+".recreate-missing-state-from", InitConfigDefault.RecreateMissingStateFrom, "block number to start recreating missing states from (0 = disabled)") + f.Bool(prefix+".rebuild-local-wasm", InitConfigDefault.RebuildLocalWasm, "rebuild local wasm database on boot if needed (otherwise-will be done lazily)") } func (c *InitConfig) Validate() error { if c.Force && c.RecreateMissingStateFrom > 0 { log.Warn("force init enabled, recreate-missing-state-from will have no effect") } + if c.Latest != "" && !isAcceptedSnapshotKind(c.Latest) { + return fmt.Errorf("invalid value for latest option: \"%s\" %s", c.Latest, acceptedSnapshotKindsStr) + } + if c.Prune != "" && c.PruneThreads <= 0 { + return fmt.Errorf("invalid number of pruning threads: %d, has to be greater then 0", c.PruneThreads) + } + if c.PruneTrieCleanCache < 0 { + return fmt.Errorf("invalid trie clean cache size: %d, has to be greater or equal 0", c.PruneTrieCleanCache) + } return nil } + +var ( + acceptedSnapshotKinds = []string{"archive", "pruned", "genesis"} + acceptedSnapshotKindsStr = "(accepted values: \"" + strings.Join(acceptedSnapshotKinds, "\" | \"") + "\")" +) + +func isAcceptedSnapshotKind(kind string) bool { + for _, valid := range acceptedSnapshotKinds { + if kind == valid { + return true + } + } + return false +} diff --git a/cmd/daserver/daserver.go b/cmd/daserver/daserver.go index 07481651b..3c164066d 100644 --- a/cmd/daserver/daserver.go +++ b/cmd/daserver/daserver.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "io" "net/http" "os" "os/signal" @@ -30,10 +31,11 @@ import ( ) type DAServerConfig struct { - EnableRPC bool `koanf:"enable-rpc"` - RPCAddr string `koanf:"rpc-addr"` - RPCPort uint64 `koanf:"rpc-port"` - RPCServerTimeouts genericconf.HTTPServerTimeoutConfig `koanf:"rpc-server-timeouts"` + EnableRPC bool `koanf:"enable-rpc"` + RPCAddr string `koanf:"rpc-addr"` + RPCPort uint64 `koanf:"rpc-port"` + RPCServerTimeouts genericconf.HTTPServerTimeoutConfig `koanf:"rpc-server-timeouts"` + RPCServerBodyLimit int `koanf:"rpc-server-body-limit"` EnableREST bool `koanf:"enable-rest"` RESTAddr string `koanf:"rest-addr"` @@ -43,7 +45,7 @@ type DAServerConfig struct { DataAvailability das.DataAvailabilityConfig `koanf:"data-availability"` Conf genericconf.ConfConfig `koanf:"conf"` - LogLevel int `koanf:"log-level"` + LogLevel string `koanf:"log-level"` LogType string `koanf:"log-type"` Metrics bool `koanf:"metrics"` @@ -57,13 +59,14 @@ var DefaultDAServerConfig = DAServerConfig{ RPCAddr: "localhost", RPCPort: 9876, RPCServerTimeouts: genericconf.HTTPServerTimeoutConfigDefault, + RPCServerBodyLimit: genericconf.HTTPServerBodyLimitDefault, EnableREST: false, RESTAddr: "localhost", RESTPort: 9877, RESTServerTimeouts: genericconf.HTTPServerTimeoutConfigDefault, DataAvailability: das.DefaultDataAvailabilityConfig, Conf: genericconf.ConfConfigDefault, - LogLevel: int(log.LvlInfo), + LogLevel: "INFO", LogType: "plaintext", Metrics: false, MetricsServer: genericconf.MetricsServerConfigDefault, @@ -87,6 +90,7 @@ func parseDAServer(args []string) (*DAServerConfig, error) { f.Bool("enable-rpc", DefaultDAServerConfig.EnableRPC, "enable the HTTP-RPC server listening on rpc-addr and rpc-port") f.String("rpc-addr", DefaultDAServerConfig.RPCAddr, "HTTP-RPC server listening interface") f.Uint64("rpc-port", DefaultDAServerConfig.RPCPort, "HTTP-RPC server listening port") + f.Int("rpc-server-body-limit", DefaultDAServerConfig.RPCServerBodyLimit, "HTTP-RPC server maximum request body size in bytes; the default (0) uses geth's 5MB limit") genericconf.HTTPServerTimeoutConfigAddOptions("rpc-server-timeouts", f) f.Bool("enable-rest", DefaultDAServerConfig.EnableREST, "enable the REST server listening on rest-addr and rest-port") @@ -100,7 +104,7 @@ func parseDAServer(args []string) (*DAServerConfig, error) { f.Bool("pprof", DefaultDAServerConfig.PProf, "enable pprof") genericconf.PProfAddOptions("pprof-cfg", f) - f.Int("log-level", int(log.LvlInfo), "log level; 1: ERROR, 2: WARN, 3: INFO, 4: DEBUG, 5: TRACE") + f.String("log-level", DefaultDAServerConfig.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") f.String("log-type", DefaultDAServerConfig.LogType, "log type (plaintext or json)") das.DataAvailabilityConfigAddDaserverOptions("data-availability", f) @@ -182,14 +186,19 @@ func startup() error { confighelpers.PrintErrorAndExit(errors.New("please specify at least one of --enable-rest or --enable-rpc"), printSampleUsage) } - logFormat, err := genericconf.ParseLogType(serverConfig.LogType) + logLevel, err := genericconf.ToSlogLevel(serverConfig.LogLevel) + if err != nil { + confighelpers.PrintErrorAndExit(err, printSampleUsage) + } + + handler, err := genericconf.HandlerFromLogType(serverConfig.LogType, io.Writer(os.Stderr)) if err != nil { flag.Usage() - panic(fmt.Sprintf("Error parsing log type: %v", err)) + return fmt.Errorf("error parsing log type when creating handler: %w", err) } - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, logFormat)) - glogger.Verbosity(log.Lvl(serverConfig.LogLevel)) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler(handler) + glogger.Verbosity(logLevel) + log.SetDefault(log.NewLogger(glogger)) if err := startMetrics(serverConfig); err != nil { return err @@ -229,7 +238,7 @@ func startup() error { return errors.New("sequencer-inbox-address must be set to a valid L1 URL and contract address, or 'none'") } - daReader, daWriter, daHealthChecker, dasLifecycleManager, err := das.CreateDAComponentsForDaserver(ctx, &serverConfig.DataAvailability, l1Reader, seqInboxAddress) + daReader, daWriter, signatureVerifier, daHealthChecker, dasLifecycleManager, err := das.CreateDAComponentsForDaserver(ctx, &serverConfig.DataAvailability, l1Reader, seqInboxAddress) if err != nil { return err } @@ -244,7 +253,7 @@ func startup() error { if serverConfig.EnableRPC { log.Info("Starting HTTP-RPC server", "addr", serverConfig.RPCAddr, "port", serverConfig.RPCPort, "revision", vcsRevision, "vcs.time", vcsTime) - rpcServer, err = das.StartDASRPCServer(ctx, serverConfig.RPCAddr, serverConfig.RPCPort, serverConfig.RPCServerTimeouts, daReader, daWriter, daHealthChecker) + rpcServer, err = das.StartDASRPCServer(ctx, serverConfig.RPCAddr, serverConfig.RPCPort, serverConfig.RPCServerTimeouts, serverConfig.RPCServerBodyLimit, daReader, daWriter, daHealthChecker, signatureVerifier) if err != nil { return err } diff --git a/cmd/dataavailability/data_availability_check.go b/cmd/dataavailability/data_availability_check.go index 72a311a7b..d80c0475b 100644 --- a/cmd/dataavailability/data_availability_check.go +++ b/cmd/dataavailability/data_availability_check.go @@ -21,7 +21,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/cmd/util/confighelpers" "github.com/offchainlabs/nitro/das" "github.com/offchainlabs/nitro/solgen/go/bridgegen" @@ -65,7 +65,7 @@ type DataAvailabilityCheck struct { config *DataAvailabilityCheckConfig inboxAddr *common.Address inboxContract *bridgegen.SequencerInbox - urlToReaderMap map[string]arbstate.DataAvailabilityReader + urlToReaderMap map[string]daprovider.DASReader checkInterval time.Duration } @@ -86,7 +86,7 @@ func newDataAvailabilityCheck(ctx context.Context, dataAvailabilityCheckConfig * if err != nil { return nil, err } - urlToReaderMap := make(map[string]arbstate.DataAvailabilityReader, len(onlineUrls)) + urlToReaderMap := make(map[string]daprovider.DASReader, len(onlineUrls)) for _, url := range onlineUrls { reader, err := das.NewRestfulDasClientFromURL(url) if err != nil { @@ -238,7 +238,7 @@ func (d *DataAvailabilityCheck) checkDataAvailability(ctx context.Context, deliv if data == nil { return false, nil } - cert, err := arbstate.DeserializeDASCertFrom(bytes.NewReader(data)) + cert, err := daprovider.DeserializeDASCertFrom(bytes.NewReader(data)) if err != nil { return true, err } diff --git a/cmd/datool/datool.go b/cmd/datool/datool.go index d78d975fd..4017457ba 100644 --- a/cmd/datool/datool.go +++ b/cmd/datool/datool.go @@ -22,7 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/cmd/util" @@ -91,6 +91,7 @@ type ClientStoreConfig struct { SigningKey string `koanf:"signing-key"` SigningWallet string `koanf:"signing-wallet"` SigningWalletPassword string `koanf:"signing-wallet-password"` + MaxStoreChunkBodySize int `koanf:"max-store-chunk-body-size"` } func parseClientStoreConfig(args []string) (*ClientStoreConfig, error) { @@ -102,6 +103,7 @@ func parseClientStoreConfig(args []string) (*ClientStoreConfig, error) { f.String("signing-wallet", "", "wallet containing ecdsa key to sign the message with") f.String("signing-wallet-password", genericconf.PASSWORD_NOT_SET, "password to unlock the wallet, if not specified the user is prompted for the password") f.Duration("das-retention-period", 24*time.Hour, "The period which DASes are requested to retain the stored batches.") + f.Int("max-store-chunk-body-size", 512*1024, "The maximum HTTP POST body size for a chunked store request") k, err := confighelpers.BeginCommonParse(f, args) if err != nil { @@ -121,12 +123,7 @@ func startClientStore(args []string) error { return err } - client, err := das.NewDASRPCClient(config.URL) - if err != nil { - return err - } - - var dasClient das.DataAvailabilityServiceWriter = client + var signer signature.DataSignerFunc if config.SigningKey != "" { var privateKey *ecdsa.PrivateKey if config.SigningKey[:2] == "0x" { @@ -140,12 +137,7 @@ func startClientStore(args []string) error { return err } } - signer := signature.DataSignerFromPrivateKey(privateKey) - - dasClient, err = das.NewStoreSigningDAS(dasClient, signer) - if err != nil { - return err - } + signer = signature.DataSignerFromPrivateKey(privateKey) } else if config.SigningWallet != "" { walletConf := &genericconf.WalletConfig{ Pathname: config.SigningWallet, @@ -154,18 +146,19 @@ func startClientStore(args []string) error { Account: "", OnlyCreateKey: false, } - _, signer, err := util.OpenWallet("datool", walletConf, nil) - if err != nil { - return err - } - dasClient, err = das.NewStoreSigningDAS(dasClient, signer) + _, signer, err = util.OpenWallet("datool", walletConf, nil) if err != nil { return err } } + client, err := das.NewDASRPCClient(config.URL, signer, config.MaxStoreChunkBodySize) + if err != nil { + return err + } + ctx := context.Background() - var cert *arbstate.DataAvailabilityCertificate + var cert *daprovider.DataAvailabilityCertificate if config.RandomMessageSize > 0 { message := make([]byte, config.RandomMessageSize) @@ -173,9 +166,9 @@ func startClientStore(args []string) error { if err != nil { return err } - cert, err = dasClient.Store(ctx, message, uint64(time.Now().Add(config.DASRetentionPeriod).Unix()), []byte{}) + cert, err = client.Store(ctx, message, uint64(time.Now().Add(config.DASRetentionPeriod).Unix())) } else if len(config.Message) > 0 { - cert, err = dasClient.Store(ctx, []byte(config.Message), uint64(time.Now().Add(config.DASRetentionPeriod).Unix()), []byte{}) + cert, err = client.Store(ctx, []byte(config.Message), uint64(time.Now().Add(config.DASRetentionPeriod).Unix())) } else { return errors.New("--message or --random-message-size must be specified") } @@ -184,7 +177,7 @@ func startClientStore(args []string) error { return err } - serializedCert := das.Serialize(cert) + serializedCert := daprovider.Serialize(cert) fmt.Printf("Hex Encoded Cert: %s\n", hexutil.Encode(serializedCert)) fmt.Printf("Hex Encoded Data Hash: %s\n", hexutil.Encode(cert.DataHash[:])) @@ -361,7 +354,7 @@ func dumpKeyset(args []string) error { return err } - services, err := das.ParseServices(config.Keyset) + services, err := das.ParseServices(config.Keyset, nil) if err != nil { return err } diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index 7f822eff6..580a25c29 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -8,6 +8,7 @@ import ( "encoding/json" "flag" "fmt" + "io" "math/big" "os" "strings" @@ -30,9 +31,10 @@ import ( ) func main() { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) glogger.Verbosity(log.LvlDebug) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) log.Info("deploying rollup") ctx := context.Background() diff --git a/cmd/genericconf/config.go b/cmd/genericconf/config.go index 50aafbe22..06e1fcd12 100644 --- a/cmd/genericconf/config.go +++ b/cmd/genericconf/config.go @@ -5,11 +5,13 @@ package genericconf import ( "errors" + "io" "time" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" flag "github.com/spf13/pflag" + "golang.org/x/exp/slog" ) type ConfConfig struct { @@ -63,11 +65,11 @@ var DefaultS3Config = S3Config{ SecretKey: "", } -func ParseLogType(logType string) (log.Format, error) { +func HandlerFromLogType(logType string, output io.Writer) (slog.Handler, error) { if logType == "plaintext" { - return log.TerminalFormat(false), nil + return log.NewTerminalHandler(output, false), nil } else if logType == "json" { - return log.JSONFormat(), nil + return log.JSONHandler(output), nil } return nil, errors.New("invalid log type") } diff --git a/cmd/genericconf/filehandler_test.go b/cmd/genericconf/filehandler_test.go index 7ea066822..daa9ed397 100644 --- a/cmd/genericconf/filehandler_test.go +++ b/cmd/genericconf/filehandler_test.go @@ -72,9 +72,10 @@ func testFileHandler(t *testing.T, testCompressed bool) { config.MaxSize = 1 config.Compress = testCompressed config.File = testFile - fileHandler := globalFileHandlerFactory.newHandler(log.JSONFormat(), &config, testFile) - defer func() { testhelpers.RequireImpl(t, globalFileHandlerFactory.close()) }() - log.Root().SetHandler(fileHandler) + handler, err := HandlerFromLogType("json", globalFileLoggerFactory.newFileWriter(&config, testFile)) + defer func() { testhelpers.RequireImpl(t, globalFileLoggerFactory.close()) }() + testhelpers.RequireImpl(t, err) + log.SetDefault(log.NewLogger(handler)) expected := []string{"dead", "beef", "ate", "bad", "beef"} for _, e := range expected { log.Warn(e) diff --git a/cmd/genericconf/logging.go b/cmd/genericconf/logging.go index a50dfa319..fa4595327 100644 --- a/cmd/genericconf/logging.go +++ b/cmd/genericconf/logging.go @@ -4,22 +4,46 @@ import ( "context" "flag" "fmt" + "io" "os" + "sync" "github.com/ethereum/go-ethereum/log" "gopkg.in/natefinch/lumberjack.v2" ) -var globalFileHandlerFactory = fileHandlerFactory{} +var globalFileLoggerFactory = fileLoggerFactory{} -type fileHandlerFactory struct { - writer *lumberjack.Logger - records chan *log.Record - cancel context.CancelFunc +type fileLoggerFactory struct { + // writerMutex is to avoid parallel writes to the file-logger + writerMutex sync.Mutex + writer *lumberjack.Logger + + cancel context.CancelFunc + + // writeStartPing and writeDonePing are used to simulate sending of data via a buffered channel + // when Write is called and receiving it on another go-routine to write it to the io.Writer. + writeStartPing chan struct{} + writeDonePing chan struct{} +} + +// Write is essentially a wrapper for filewriter or lumberjack.Logger's Write method to implement +// config.BufSize functionality, data is dropped when l.writeStartPing channel (of size config.BuffSize) is full +func (l *fileLoggerFactory) Write(p []byte) (n int, err error) { + select { + case l.writeStartPing <- struct{}{}: + // Write data to the filelogger + l.writerMutex.Lock() + _, _ = l.writer.Write(p) + l.writerMutex.Unlock() + l.writeDonePing <- struct{}{} + default: + } + return len(p), nil } -// newHandler is not threadsafe -func (l *fileHandlerFactory) newHandler(logFormat log.Format, config *FileLoggingConfig, filename string) log.Handler { +// newFileWriter is not threadsafe +func (l *fileLoggerFactory) newFileWriter(config *FileLoggingConfig, filename string) io.Writer { l.close() l.writer = &lumberjack.Logger{ Filename: filename, @@ -28,40 +52,29 @@ func (l *fileHandlerFactory) newHandler(logFormat log.Format, config *FileLoggin MaxAge: config.MaxAge, Compress: config.Compress, } - // capture copy of the pointer - writer := l.writer - // lumberjack.Logger already locks on Write, no need for SyncHandler proxy which is used in StreamHandler - unsafeStreamHandler := log.LazyHandler(log.FuncHandler(func(r *log.Record) error { - _, err := writer.Write(logFormat.Format(r)) - return err - })) - l.records = make(chan *log.Record, config.BufSize) + l.writeStartPing = make(chan struct{}, config.BufSize) + l.writeDonePing = make(chan struct{}, config.BufSize) // capture copy - records := l.records + writeStartPing := l.writeStartPing + writeDonePing := l.writeDonePing var consumerCtx context.Context consumerCtx, l.cancel = context.WithCancel(context.Background()) go func() { + // writeStartPing channel signals Write operations to correctly implement config.BufSize functionality for { select { - case r := <-records: - _ = unsafeStreamHandler.Log(r) + case <-writeStartPing: + <-writeDonePing case <-consumerCtx.Done(): return } } }() - return log.FuncHandler(func(r *log.Record) error { - select { - case records <- r: - return nil - default: - return fmt.Errorf("Buffer overflow, dropping record") - } - }) + return l } // close is not threadsafe -func (l *fileHandlerFactory) close() error { +func (l *fileLoggerFactory) close() error { if l.cancel != nil { l.cancel() l.cancel = nil @@ -76,28 +89,35 @@ func (l *fileHandlerFactory) close() error { } // initLog is not threadsafe -func InitLog(logType string, logLevel log.Lvl, fileLoggingConfig *FileLoggingConfig, pathResolver func(string) string) error { - logFormat, err := ParseLogType(logType) - if err != nil { - flag.Usage() - return fmt.Errorf("error parsing log type: %w", err) - } +func InitLog(logType string, logLevel string, fileLoggingConfig *FileLoggingConfig, pathResolver func(string) string) error { var glogger *log.GlogHandler // always close previous instance of file logger - if err := globalFileHandlerFactory.close(); err != nil { + if err := globalFileLoggerFactory.close(); err != nil { return fmt.Errorf("failed to close file writer: %w", err) } + var output io.Writer if fileLoggingConfig.Enable { - glogger = log.NewGlogHandler( - log.MultiHandler( - log.StreamHandler(os.Stderr, logFormat), - // on overflow records are dropped silently as MultiHandler ignores errors - globalFileHandlerFactory.newHandler(logFormat, fileLoggingConfig, pathResolver(fileLoggingConfig.File)), - )) + output = io.MultiWriter( + io.Writer(os.Stderr), + // on overflow writeStartPing are dropped silently + globalFileLoggerFactory.newFileWriter(fileLoggingConfig, pathResolver(fileLoggingConfig.File)), + ) } else { - glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, logFormat)) + output = io.Writer(os.Stderr) + } + handler, err := HandlerFromLogType(logType, output) + if err != nil { + flag.Usage() + return fmt.Errorf("error parsing log type when creating handler: %w", err) } - glogger.Verbosity(logLevel) - log.Root().SetHandler(glogger) + slogLevel, err := ToSlogLevel(logLevel) + if err != nil { + flag.Usage() + return fmt.Errorf("error parsing log level: %w", err) + } + + glogger = log.NewGlogHandler(handler) + glogger.Verbosity(slogLevel) + log.SetDefault(log.NewLogger(glogger)) return nil } diff --git a/cmd/genericconf/loglevel.go b/cmd/genericconf/loglevel.go new file mode 100644 index 000000000..f7ad05a2c --- /dev/null +++ b/cmd/genericconf/loglevel.go @@ -0,0 +1,38 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package genericconf + +import ( + "errors" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slog" +) + +func ToSlogLevel(str string) (slog.Level, error) { + switch strings.ToLower(str) { + case "trace": + return log.LevelTrace, nil + case "debug": + return log.LevelDebug, nil + case "info": + return log.LevelInfo, nil + case "warn": + return log.LevelWarn, nil + case "error": + return log.LevelError, nil + case "crit": + return log.LevelCrit, nil + default: + legacyLevel, err := strconv.Atoi(str) + if err != nil { + // Leave legacy geth numeric log levels undocumented, but if anyone happens + // to be using them, it will work. + return log.LevelTrace, errors.New("invalid log-level") + } + return log.FromLegacyLevel(legacyLevel), nil + } +} diff --git a/cmd/genericconf/server.go b/cmd/genericconf/server.go index 7550791d6..9b8acd5f7 100644 --- a/cmd/genericconf/server.go +++ b/cmd/genericconf/server.go @@ -48,6 +48,8 @@ var HTTPServerTimeoutConfigDefault = HTTPServerTimeoutConfig{ IdleTimeout: 120 * time.Second, } +const HTTPServerBodyLimitDefault = 0 // Use default from go-ethereum + func (c HTTPConfig) Apply(stackConf *node.Config) { stackConf.HTTPHost = c.Addr stackConf.HTTPPort = c.Port diff --git a/cmd/ipfshelper/ipfshelper.go b/cmd/ipfshelper/ipfshelper.bkup_go similarity index 99% rename from cmd/ipfshelper/ipfshelper.go rename to cmd/ipfshelper/ipfshelper.bkup_go index 82e726dbf..ccde492ca 100644 --- a/cmd/ipfshelper/ipfshelper.go +++ b/cmd/ipfshelper/ipfshelper.bkup_go @@ -1,3 +1,6 @@ +//go:build ipfs +// +build ipfs + package ipfshelper import ( diff --git a/cmd/ipfshelper/ipfshelper_stub.go b/cmd/ipfshelper/ipfshelper_stub.go new file mode 100644 index 000000000..fa6a45192 --- /dev/null +++ b/cmd/ipfshelper/ipfshelper_stub.go @@ -0,0 +1,31 @@ +//go:build !ipfs +// +build !ipfs + +package ipfshelper + +import ( + "context" + "errors" +) + +type IpfsHelper struct{} + +var ErrIpfsNotSupported = errors.New("ipfs not supported") + +var DefaultIpfsProfiles = "default ipfs profiles stub" + +func CanBeIpfsPath(pathString string) bool { + return false +} + +func CreateIpfsHelper(ctx context.Context, downloadPath string, clientOnly bool, peerList []string, profiles string) (*IpfsHelper, error) { + return nil, ErrIpfsNotSupported +} + +func (h *IpfsHelper) DownloadFile(ctx context.Context, cidString string, destinationDir string) (string, error) { + return "", ErrIpfsNotSupported +} + +func (h *IpfsHelper) Close() error { + return ErrIpfsNotSupported +} diff --git a/cmd/ipfshelper/ipfshelper_test.go b/cmd/ipfshelper/ipfshelper_test.go index 052d6bab0..80f10c21f 100644 --- a/cmd/ipfshelper/ipfshelper_test.go +++ b/cmd/ipfshelper/ipfshelper_test.go @@ -1,3 +1,6 @@ +//go:build ipfs +// +build ipfs + package ipfshelper import ( diff --git a/cmd/nitro-val/config.go b/cmd/nitro-val/config.go index 51d397883..b52a1c6b5 100644 --- a/cmd/nitro-val/config.go +++ b/cmd/nitro-val/config.go @@ -2,10 +2,10 @@ package main import ( "fmt" + "reflect" "time" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/nat" @@ -20,7 +20,7 @@ import ( type ValidationNodeConfig struct { Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` Validation valnode.Config `koanf:"validation" reload:"hot"` - LogLevel int `koanf:"log-level" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` LogType string `koanf:"log-type" reload:"hot"` FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` Persistent conf.PersistentConfig `koanf:"persistent"` @@ -61,7 +61,7 @@ var IPCConfigDefault = genericconf.IPCConfig{ var ValidationNodeConfigDefault = ValidationNodeConfig{ Conf: genericconf.ConfConfigDefault, - LogLevel: int(log.LvlInfo), + LogLevel: "INFO", LogType: "plaintext", Persistent: conf.PersistentConfigDefault, HTTP: HTTPConfigDefault, @@ -79,7 +79,7 @@ var ValidationNodeConfigDefault = ValidationNodeConfig{ func ValidationNodeConfigAddOptions(f *flag.FlagSet) { genericconf.ConfConfigAddOptions("conf", f) valnode.ValidationConfigAddOptions("validation", f) - f.Int("log-level", ValidationNodeConfigDefault.LogLevel, "log level") + f.String("log-level", ValidationNodeConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") f.String("log-type", ValidationNodeConfigDefault.LogType, "log type (plaintext or json)") genericconf.FileLoggingConfigAddOptions("file-logging", f) conf.PersistentConfigAddOptions("persistent", f) diff --git a/cmd/nitro-val/nitro_val.go b/cmd/nitro-val/nitro_val.go index fea95cbb1..1e894336e 100644 --- a/cmd/nitro-val/nitro_val.go +++ b/cmd/nitro-val/nitro_val.go @@ -20,7 +20,7 @@ import ( "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/cmd/util/confighelpers" - _ "github.com/offchainlabs/nitro/nodeInterface" + _ "github.com/offchainlabs/nitro/execution/nodeInterface" "github.com/offchainlabs/nitro/validator/valnode" ) @@ -89,7 +89,7 @@ func mainImpl() int { } } - err = genericconf.InitLog(nodeConfig.LogType, log.Lvl(nodeConfig.LogLevel), &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + err = genericconf.InitLog(nodeConfig.LogType, nodeConfig.LogLevel, &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) if err != nil { fmt.Fprintf(os.Stderr, "Error initializing logging: %v\n", err) return 1 @@ -108,7 +108,7 @@ func mainImpl() int { liveNodeConfig := genericconf.NewLiveConfig[*ValidationNodeConfig](args, nodeConfig, ParseNode) liveNodeConfig.SetOnReloadHook(func(oldCfg *ValidationNodeConfig, newCfg *ValidationNodeConfig) error { - return genericconf.InitLog(newCfg.LogType, log.Lvl(newCfg.LogLevel), &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + return genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) }) valnode.EnsureValidationExposedViaAuthRPC(&stackConf) diff --git a/cmd/nitro/config_test.go b/cmd/nitro/config_test.go index ea04d4eb1..d76dd1b7b 100644 --- a/cmd/nitro/config_test.go +++ b/cmd/nitro/config_test.go @@ -39,26 +39,26 @@ func TestEmptyCliConfig(t *testing.T) { } func TestSeqConfig(t *testing.T) { - args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") - _, _, _, err := ParseNode(context.Background(), args) + args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --node.batch-poster.parent-chain-wallet.pathname /l1keystore --node.batch-poster.parent-chain-wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") + _, _, err := ParseNode(context.Background(), args) Require(t, err) } func TestUnsafeStakerConfig(t *testing.T) { - args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.staker.enable --node.staker.strategy MakeNodes --node.staker.staker-interval 10s --execution.forwarding-target null --node.staker.dangerous.without-block-validator", " ") - _, _, _, err := ParseNode(context.Background(), args) + args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --node.staker.parent-chain-wallet.pathname /l1keystore --node.staker.parent-chain-wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.staker.enable --node.staker.strategy MakeNodes --node.staker.staker-interval 10s --execution.forwarding-target null --node.staker.dangerous.without-block-validator", " ") + _, _, err := ParseNode(context.Background(), args) Require(t, err) } func TestValidatorConfig(t *testing.T) { - args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.staker.enable --node.staker.strategy MakeNodes --node.staker.staker-interval 10s --execution.forwarding-target null", " ") - _, _, _, err := ParseNode(context.Background(), args) + args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --node.staker.parent-chain-wallet.pathname /l1keystore --node.staker.parent-chain-wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.staker.enable --node.staker.strategy MakeNodes --node.staker.staker-interval 10s --execution.forwarding-target null", " ") + _, _, err := ParseNode(context.Background(), args) Require(t, err) } func TestAggregatorConfig(t *testing.T) { - args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642 --node.data-availability.enable --node.data-availability.rpc-aggregator.backends {[\"url\":\"http://localhost:8547\",\"pubkey\":\"abc==\",\"signerMask\":0x1]}", " ") - _, _, _, err := ParseNode(context.Background(), args) + args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --node.batch-poster.parent-chain-wallet.pathname /l1keystore --node.batch-poster.parent-chain-wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642 --node.data-availability.enable --node.data-availability.rpc-aggregator.backends {[\"url\":\"http://localhost:8547\",\"pubkey\":\"abc==\",\"signerMask\":0x1]}", " ") + _, _, err := ParseNode(context.Background(), args) Require(t, err) } @@ -120,13 +120,13 @@ func TestLiveNodeConfig(t *testing.T) { jsonConfig := "{\"chain\":{\"id\":421613}}" Require(t, WriteToConfigFile(configFile, jsonConfig)) - args := strings.Split("--file-logging.enable=false --persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") + args := strings.Split("--file-logging.enable=false --persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --node.batch-poster.parent-chain-wallet.pathname /l1keystore --node.batch-poster.parent-chain-wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") args = append(args, []string{"--conf.file", configFile}...) - config, _, _, err := ParseNode(context.Background(), args) + config, _, err := ParseNode(context.Background(), args) Require(t, err) liveConfig := genericconf.NewLiveConfig[*NodeConfig](args, config, func(ctx context.Context, args []string) (*NodeConfig, error) { - nodeConfig, _, _, err := ParseNode(ctx, args) + nodeConfig, _, err := ParseNode(ctx, args) return nodeConfig, err }) @@ -201,13 +201,13 @@ func TestPeriodicReloadOfLiveNodeConfig(t *testing.T) { jsonConfig := "{\"conf\":{\"reload-interval\":\"20ms\"}}" Require(t, WriteToConfigFile(configFile, jsonConfig)) - args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") + args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --node.batch-poster.parent-chain-wallet.pathname /l1keystore --node.batch-poster.parent-chain-wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") args = append(args, []string{"--conf.file", configFile}...) - config, _, _, err := ParseNode(context.Background(), args) + config, _, err := ParseNode(context.Background(), args) Require(t, err) liveConfig := genericconf.NewLiveConfig[*NodeConfig](args, config, func(ctx context.Context, args []string) (*NodeConfig, error) { - nodeConfig, _, _, err := ParseNode(ctx, args) + nodeConfig, _, err := ParseNode(ctx, args) return nodeConfig, err }) liveConfig.Start(ctx) diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 72c767d00..49d30fd59 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -5,11 +5,18 @@ package main import ( "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" + "io" "math/big" + "net/http" + "net/url" "os" + "path" + "path/filepath" "runtime" "strings" "sync" @@ -40,6 +47,8 @@ import ( "github.com/offchainlabs/nitro/util/arbmath" ) +var notFoundError = errors.New("file not found") + func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, error) { if initConfig.Url == "" { return "", nil @@ -66,18 +75,30 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err } return initFile, nil } - grabclient := grab.NewClient() log.Info("Downloading initial database", "url", initConfig.Url) - fmt.Println() + path, err := downloadFile(ctx, initConfig, initConfig.Url) + if errors.Is(err, notFoundError) { + return downloadInitInParts(ctx, initConfig) + } + return path, err +} + +func downloadFile(ctx context.Context, initConfig *conf.InitConfig, url string) (string, error) { + checksum, err := fetchChecksum(ctx, url+".sha256") + if err != nil { + return "", fmt.Errorf("error fetching checksum: %w", err) + } + grabclient := grab.NewClient() printTicker := time.NewTicker(time.Second) defer printTicker.Stop() attempt := 0 for { attempt++ - req, err := grab.NewRequest(initConfig.DownloadPath, initConfig.Url) + req, err := grab.NewRequest(initConfig.DownloadPath, url) if err != nil { panic(err) } + req.SetChecksum(sha256.New(), checksum, false) resp := grabclient.Do(req.WithContext(ctx)) firstPrintTime := time.Now().Add(time.Second * 2) updateLoop: @@ -102,6 +123,9 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err } case <-resp.Done: if err := resp.Err(); err != nil { + if resp.HTTPResponse.StatusCode == http.StatusNotFound { + return "", fmt.Errorf("file not found but checksum exists") + } fmt.Printf("\n attempt %d failed: %v\n", attempt, err) break updateLoop } @@ -121,6 +145,130 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err } } +// httpGet performs a GET request to the specified URL +func httpGet(ctx context.Context, url string) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making GET request: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return nil, notFoundError + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %v", resp.Status) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + return body, nil +} + +// fetchChecksum performs a GET request to the specified URL and returns the checksum +func fetchChecksum(ctx context.Context, url string) ([]byte, error) { + body, err := httpGet(ctx, url) + if err != nil { + return nil, err + } + checksumStr := strings.TrimSpace(string(body)) + checksum, err := hex.DecodeString(checksumStr) + if err != nil { + return nil, fmt.Errorf("error decoding checksum: %w", err) + } + if len(checksum) != sha256.Size { + return nil, fmt.Errorf("invalid checksum length") + } + return checksum, nil +} + +func downloadInitInParts(ctx context.Context, initConfig *conf.InitConfig) (string, error) { + log.Info("File not found; trying to download database in parts") + fileInfo, err := os.Stat(initConfig.DownloadPath) + if err != nil || !fileInfo.IsDir() { + return "", fmt.Errorf("download path must be a directory: %v", initConfig.DownloadPath) + } + part := 0 + parts := []string{} + defer func() { + // remove all temporary files. + for _, part := range parts { + err := os.Remove(part) + if err != nil { + log.Warn("Failed to remove temporary file", "file", part) + } + } + }() + for { + url := fmt.Sprintf("%s.part%d", initConfig.Url, part) + log.Info("Downloading database part", "url", url) + partFile, err := downloadFile(ctx, initConfig, url) + if errors.Is(err, notFoundError) { + log.Info("Part not found; concatenating archive into single file", "numParts", len(parts)) + break + } else if err != nil { + return "", err + } + parts = append(parts, partFile) + part++ + } + return joinArchive(parts) +} + +// joinArchive joins the archive parts into a single file and return its path. +func joinArchive(parts []string) (string, error) { + if len(parts) == 0 { + return "", fmt.Errorf("no database parts found") + } + archivePath := strings.TrimSuffix(parts[0], ".part0") + archive, err := os.Create(archivePath) + if err != nil { + return "", fmt.Errorf("failed to create archive: %w", err) + } + defer archive.Close() + for _, part := range parts { + partFile, err := os.Open(part) + if err != nil { + return "", fmt.Errorf("failed to open part file %s: %w", part, err) + } + defer partFile.Close() + _, err = io.Copy(archive, partFile) + if err != nil { + return "", fmt.Errorf("failed to copy part file %s: %w", part, err) + } + log.Info("Joined database part into archive", "part", part) + } + log.Info("Successfully joined parts into archive", "archive", archivePath) + return archivePath, nil +} + +// setLatestSnapshotUrl sets the Url in initConfig to the latest one available on the mirror. +func setLatestSnapshotUrl(ctx context.Context, initConfig *conf.InitConfig, chain string) error { + if initConfig.Latest == "" { + return nil + } + if initConfig.Url != "" { + return fmt.Errorf("cannot set latest url if url is already set") + } + baseUrl, err := url.Parse(initConfig.LatestBase) + if err != nil { + return fmt.Errorf("failed to parse latest mirror \"%s\": %w", initConfig.LatestBase, err) + } + latestDateUrl := baseUrl.JoinPath(chain, "latest-"+initConfig.Latest+".txt").String() + latestDateBytes, err := httpGet(ctx, latestDateUrl) + if err != nil { + return fmt.Errorf("failed to get latest snapshot at \"%s\": %w", latestDateUrl, err) + } + latestDate := strings.TrimSpace(string(latestDateBytes)) + initConfig.Url = baseUrl.JoinPath(chain, latestDate, initConfig.Latest+".tar").String() + log.Info("Set latest snapshot url", "url", initConfig.Url) + return nil +} + func validateBlockChain(blockChain *core.BlockChain, chainConfig *params.ChainConfig) error { statedb, err := blockChain.State() if err != nil { @@ -155,23 +303,47 @@ func validateBlockChain(blockChain *core.BlockChain, chainConfig *params.ChainCo return fmt.Errorf("invalid chain config, not compatible with previous: %w", err) } } + // Make sure we don't allow accidentally downgrading ArbOS + if chainConfig.DebugMode() { + if currentArbosState.ArbOSVersion() > currentArbosState.MaxDebugArbosVersionSupported() { + return fmt.Errorf("attempted to launch node in debug mode with ArbOS version %v on ArbOS state with version %v", currentArbosState.MaxDebugArbosVersionSupported(), currentArbosState.ArbOSVersion()) + } + } else { + if currentArbosState.ArbOSVersion() > currentArbosState.MaxArbosVersionSupported() { + return fmt.Errorf("attempted to launch node with ArbOS version %v on ArbOS state with version %v", currentArbosState.MaxArbosVersionSupported(), currentArbosState.ArbOSVersion()) + } + + } return nil } -func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) { +func dirExists(path string) bool { + info, err := os.Stat(path) + if os.IsNotExist(err) { + return false + } + return info.IsDir() +} + +func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) { if !config.Init.Force { - if readOnlyDb, err := stack.OpenDatabaseWithFreezer("l2chaindata", 0, 0, "", "", true); err == nil { + if readOnlyDb, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata", 0, 0, "", "l2chaindata/", true, persistentConfig.Pebble.ExtraOptions("l2chaindata")); err == nil { if chainConfig := gethexec.TryReadStoredChainConfig(readOnlyDb); chainConfig != nil { readOnlyDb.Close() if !arbmath.BigEquals(chainConfig.ChainID, chainId) { return nil, nil, fmt.Errorf("database has chain ID %v but config has chain ID %v (are you sure this database is for the right chain?)", chainConfig.ChainID, chainId) } - chainDb, err := stack.OpenDatabaseWithFreezer("l2chaindata", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "", false) + chainData, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "l2chaindata/", false, persistentConfig.Pebble.ExtraOptions("l2chaindata")) if err != nil { - return chainDb, nil, err + return nil, nil, err + } + wasmDb, err := stack.OpenDatabaseWithExtraOptions("wasm", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, "wasm/", false, persistentConfig.Pebble.ExtraOptions("wasm")) + if err != nil { + return nil, nil, err } - err = pruning.PruneChainDb(ctx, chainDb, stack, &config.Init, cacheConfig, l1Client, rollupAddrs, config.Node.ValidatorRequired()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb, 1) + err = pruning.PruneChainDb(ctx, chainDb, stack, &config.Init, cacheConfig, persistentConfig, l1Client, rollupAddrs, config.Node.ValidatorRequired()) if err != nil { return chainDb, nil, fmt.Errorf("error pruning: %w", err) } @@ -189,13 +361,78 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo return chainDb, l2BlockChain, fmt.Errorf("failed to recreate missing states: %w", err) } } - + latestBlock := l2BlockChain.CurrentBlock() + if latestBlock == nil || latestBlock.Number.Uint64() <= chainConfig.ArbitrumChainParams.GenesisBlockNum || + types.DeserializeHeaderExtraInformation(latestBlock).ArbOSFormatVersion < params.ArbosVersion_Stylus { + // If there is only genesis block or no blocks in the blockchain, set Rebuilding of wasm store to Done + // If Stylus upgrade hasn't yet happened, skipping rebuilding of wasm store + log.Info("Setting rebuilding of wasm store to done") + if err = gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingPositionKey, gethexec.RebuildingDone); err != nil { + return nil, nil, fmt.Errorf("unable to set rebuilding status of wasm store to done: %w", err) + } + } else if config.Init.RebuildLocalWasm { + position, err := gethexec.ReadFromKeyValueStore[common.Hash](wasmDb, gethexec.RebuildingPositionKey) + if err != nil { + log.Info("Unable to get codehash position in rebuilding of wasm store, its possible it isnt initialized yet, so initializing it and starting rebuilding", "err", err) + if err := gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingPositionKey, common.Hash{}); err != nil { + return nil, nil, fmt.Errorf("unable to initialize codehash position in rebuilding of wasm store to beginning: %w", err) + } + } + if position != gethexec.RebuildingDone { + startBlockHash, err := gethexec.ReadFromKeyValueStore[common.Hash](wasmDb, gethexec.RebuildingStartBlockHashKey) + if err != nil { + log.Info("Unable to get start block hash in rebuilding of wasm store, its possible it isnt initialized yet, so initializing it to latest block hash", "err", err) + if err := gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingStartBlockHashKey, latestBlock.Hash()); err != nil { + return nil, nil, fmt.Errorf("unable to initialize start block hash in rebuilding of wasm store to latest block hash: %w", err) + } + startBlockHash = latestBlock.Hash() + } + log.Info("Starting or continuing rebuilding of wasm store", "codeHash", position, "startBlockHash", startBlockHash) + if err := gethexec.RebuildWasmStore(ctx, wasmDb, chainDb, config.Execution.RPC.MaxRecreateStateDepth, l2BlockChain, position, startBlockHash); err != nil { + return nil, nil, fmt.Errorf("error rebuilding of wasm store: %w", err) + } + } + } return chainDb, l2BlockChain, nil } readOnlyDb.Close() } } + // Check if database was misplaced in parent dir + const errorFmt = "database was not found in %s, but it was found in %s (have you placed the database in the wrong directory?)" + parentDir := filepath.Dir(stack.InstanceDir()) + if dirExists(path.Join(parentDir, "l2chaindata")) { + return nil, nil, fmt.Errorf(errorFmt, stack.InstanceDir(), parentDir) + } + grandParentDir := filepath.Dir(parentDir) + if dirExists(path.Join(grandParentDir, "l2chaindata")) { + return nil, nil, fmt.Errorf(errorFmt, stack.InstanceDir(), grandParentDir) + } + + // Check if database directory is empty + entries, err := os.ReadDir(stack.InstanceDir()) + if err != nil { + return nil, nil, fmt.Errorf("failed to open database dir %s: %w", stack.InstanceDir(), err) + } + unexpectedFiles := []string{} + for _, entry := range entries { + if entry.Name() != "LOCK" { + unexpectedFiles = append(unexpectedFiles, entry.Name()) + } + } + if len(unexpectedFiles) > 0 { + if config.Init.Force { + return nil, nil, fmt.Errorf("trying to overwrite old database directory '%s' (delete the database directory and try again)", stack.InstanceDir()) + } + firstThreeFilenames := strings.Join(unexpectedFiles[:min(len(unexpectedFiles), 3)], ", ") + return nil, nil, fmt.Errorf("found %d unexpected files in database directory, including: %s", len(unexpectedFiles), firstThreeFilenames) + } + + if err := setLatestSnapshotUrl(ctx, &config.Init, config.Chain.Name); err != nil { + return nil, nil, err + } + initFile, err := downloadInit(ctx, &config.Init) if err != nil { return nil, nil, err @@ -219,9 +456,21 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo var initDataReader statetransfer.InitDataReader = nil - chainDb, err := stack.OpenDatabaseWithFreezer("l2chaindata", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "", false) + chainData, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "l2chaindata/", false, persistentConfig.Pebble.ExtraOptions("l2chaindata")) + if err != nil { + return nil, nil, err + } + wasmDb, err := stack.OpenDatabaseWithExtraOptions("wasm", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, "wasm/", false, persistentConfig.Pebble.ExtraOptions("wasm")) + if err != nil { + return nil, nil, err + } + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb, 1) + + // Rebuilding wasm store is not required when just starting out + err = gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingPositionKey, gethexec.RebuildingDone) + log.Info("Setting codehash position in rebuilding of wasm store to done") if err != nil { - return chainDb, nil, err + return nil, nil, fmt.Errorf("unable to set codehash position in rebuilding of wasm store to done: %w", err) } if config.Init.ImportFile != "" { @@ -367,7 +616,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo return chainDb, l2BlockChain, err } - err = pruning.PruneChainDb(ctx, chainDb, stack, &config.Init, cacheConfig, l1Client, rollupAddrs, config.Node.ValidatorRequired()) + err = pruning.PruneChainDb(ctx, chainDb, stack, &config.Init, cacheConfig, persistentConfig, l1Client, rollupAddrs, config.Node.ValidatorRequired()) if err != nil { return chainDb, nil, fmt.Errorf("error pruning: %w", err) } diff --git a/cmd/nitro/init_test.go b/cmd/nitro/init_test.go new file mode 100644 index 000000000..17bac3d67 --- /dev/null +++ b/cmd/nitro/init_test.go @@ -0,0 +1,179 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package main + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "net" + "net/http" + "os" + "path/filepath" + "testing" + "time" + + "github.com/offchainlabs/nitro/cmd/conf" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func TestDownloadInit(t *testing.T) { + const ( + archiveName = "random_data.tar.gz" + dataSize = 1024 * 1024 + filePerm = 0600 + ) + + // Create archive with random data + serverDir := t.TempDir() + data := testhelpers.RandomSlice(dataSize) + checksumBytes := sha256.Sum256(data) + checksum := hex.EncodeToString(checksumBytes[:]) + + // Write archive file + archiveFile := fmt.Sprintf("%s/%s", serverDir, archiveName) + err := os.WriteFile(archiveFile, data, filePerm) + Require(t, err, "failed to write archive") + + // Write checksum file + checksumFile := archiveFile + ".sha256" + err = os.WriteFile(checksumFile, []byte(checksum), filePerm) + Require(t, err, "failed to write checksum") + + // Start HTTP server + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + addr := startFileServer(t, ctx, serverDir) + + // Download file + initConfig := conf.InitConfigDefault + initConfig.Url = fmt.Sprintf("http://%s/%s", addr, archiveName) + initConfig.DownloadPath = t.TempDir() + receivedArchive, err := downloadInit(ctx, &initConfig) + Require(t, err, "failed to download") + + // Check archive contents + receivedData, err := os.ReadFile(receivedArchive) + Require(t, err, "failed to read received archive") + if !bytes.Equal(receivedData, data) { + t.Error("downloaded archive is different from generated one") + } +} + +func TestDownloadInitInParts(t *testing.T) { + const ( + archiveName = "random_data.tar.gz" + numParts = 3 + partSize = 1024 * 1024 + dataSize = numParts * partSize + filePerm = 0600 + ) + + // Create parts with random data + serverDir := t.TempDir() + data := testhelpers.RandomSlice(dataSize) + for i := 0; i < numParts; i++ { + // Create part and checksum + partData := data[partSize*i : partSize*(i+1)] + checksumBytes := sha256.Sum256(partData) + checksum := hex.EncodeToString(checksumBytes[:]) + // Write part file + partFile := fmt.Sprintf("%s/%s.part%d", serverDir, archiveName, i) + err := os.WriteFile(partFile, partData, filePerm) + Require(t, err, "failed to write part") + // Write checksum file + checksumFile := partFile + ".sha256" + err = os.WriteFile(checksumFile, []byte(checksum), filePerm) + Require(t, err, "failed to write checksum") + } + + // Start HTTP server + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + addr := startFileServer(t, ctx, serverDir) + + // Download file + initConfig := conf.InitConfigDefault + initConfig.Url = fmt.Sprintf("http://%s/%s", addr, archiveName) + initConfig.DownloadPath = t.TempDir() + receivedArchive, err := downloadInit(ctx, &initConfig) + Require(t, err, "failed to download") + + // check database contents + receivedData, err := os.ReadFile(receivedArchive) + Require(t, err, "failed to read received archive") + if !bytes.Equal(receivedData, data) { + t.Error("downloaded archive is different from generated one") + } + + // Check if the function deleted the temporary files + entries, err := os.ReadDir(initConfig.DownloadPath) + Require(t, err, "failed to read temp dir") + if len(entries) != 1 { + t.Error("download function did not delete temp files") + } +} + +func TestSetLatestSnapshotUrl(t *testing.T) { + const ( + chain = "arb1" + snapshotKind = "archive" + latestDate = "2024/21" + latestFile = "latest-" + snapshotKind + ".txt" + dirPerm = 0700 + filePerm = 0600 + ) + + // Create latest file + serverDir := t.TempDir() + err := os.Mkdir(filepath.Join(serverDir, chain), dirPerm) + Require(t, err) + err = os.WriteFile(filepath.Join(serverDir, chain, latestFile), []byte(latestDate), filePerm) + Require(t, err) + + // Start HTTP server + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + addr := "http://" + startFileServer(t, ctx, serverDir) + + // Set latest snapshot URL + initConfig := conf.InitConfigDefault + initConfig.Latest = snapshotKind + initConfig.LatestBase = addr + err = setLatestSnapshotUrl(ctx, &initConfig, chain) + Require(t, err) + + // Check url + want := fmt.Sprintf("%s/%s/%s/archive.tar", addr, chain, latestDate) + if initConfig.Url != want { + t.Errorf("initConfig.Url = %s; want: %s", initConfig.Url, want) + } +} + +func startFileServer(t *testing.T, ctx context.Context, dir string) string { + t.Helper() + ln, err := net.Listen("tcp", "127.0.0.1:0") + Require(t, err, "failed to listen") + addr := ln.Addr().String() + server := &http.Server{ + Addr: addr, + Handler: http.FileServer(http.Dir(dir)), + ReadHeaderTimeout: time.Second, + } + go func() { + err := server.Serve(ln) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + t.Error("failed to shutdown server") + } + }() + go func() { + <-ctx.Done() + err := server.Shutdown(ctx) + Require(t, err, "failed to shutdown server") + }() + return addr +} diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index 09c9b554e..0eaf461fa 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -6,6 +6,7 @@ package main import ( "context" "crypto/ecdsa" + "encoding/hex" "errors" "fmt" "io" @@ -42,7 +43,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/resourcemanager" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" blocksreexecutor "github.com/offchainlabs/nitro/blocks_reexecutor" "github.com/offchainlabs/nitro/cmd/chaininfo" @@ -51,7 +52,7 @@ import ( "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/cmd/util/confighelpers" "github.com/offchainlabs/nitro/execution/gethexec" - _ "github.com/offchainlabs/nitro/nodeInterface" + _ "github.com/offchainlabs/nitro/execution/nodeInterface" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" @@ -164,7 +165,7 @@ func mainImpl() int { defer cancelFunc() args := os.Args[1:] - nodeConfig, l1Wallet, l2DevWallet, err := ParseNode(ctx, args) + nodeConfig, l2DevWallet, err := ParseNode(ctx, args) if err != nil { confighelpers.PrintErrorAndExit(err, printSampleUsage) } @@ -207,7 +208,7 @@ func mainImpl() int { } stackConf.JWTSecret = filename } - err = genericconf.InitLog(nodeConfig.LogType, log.Lvl(nodeConfig.LogLevel), &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + err = genericconf.InitLog(nodeConfig.LogType, nodeConfig.LogLevel, &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) if err != nil { fmt.Fprintf(os.Stderr, "Error initializing logging: %v\n", err) return 1 @@ -231,7 +232,6 @@ func mainImpl() int { log.Error("consensus and execution must agree if sequencing is enabled or not", "Execution.Sequencer.Enable", nodeConfig.Execution.Sequencer.Enable, "Node.Sequencer", nodeConfig.Node.Sequencer) } - var l1TransactionOpts *bind.TransactOpts var dataSigner signature.DataSignerFunc var l1TransactionOptsValidator *bind.TransactOpts var l1TransactionOptsBatchPoster *bind.TransactOpts @@ -242,7 +242,6 @@ func mainImpl() int { validatorNeedsKey := nodeConfig.Node.Staker.OnlyCreateWalletContract || (nodeConfig.Node.Staker.Enable && !strings.EqualFold(nodeConfig.Node.Staker.Strategy, "watchtower") && nodeConfig.Node.Staker.DataPoster.ExternalSigner.URL == "") - l1Wallet.ResolveDirectoryNames(nodeConfig.Persistent.Chain) defaultL1WalletConfig := conf.DefaultL1WalletConfig defaultL1WalletConfig.ResolveDirectoryNames(nodeConfig.Persistent.Chain) @@ -254,42 +253,24 @@ func mainImpl() int { defaultBatchPosterL1WalletConfig := arbnode.DefaultBatchPosterL1WalletConfig defaultBatchPosterL1WalletConfig.ResolveDirectoryNames(nodeConfig.Persistent.Chain) - if nodeConfig.Node.Staker.ParentChainWallet == defaultValidatorL1WalletConfig && nodeConfig.Node.BatchPoster.ParentChainWallet == defaultBatchPosterL1WalletConfig { - if sequencerNeedsKey || validatorNeedsKey || l1Wallet.OnlyCreateKey { - l1TransactionOpts, dataSigner, err = util.OpenWallet("l1", l1Wallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID)) - if err != nil { - flag.Usage() - log.Crit("error opening parent chain wallet", "path", l1Wallet.Pathname, "account", l1Wallet.Account, "err", err) - } - if l1Wallet.OnlyCreateKey { - return 0 - } - l1TransactionOptsBatchPoster = l1TransactionOpts - l1TransactionOptsValidator = l1TransactionOpts + if sequencerNeedsKey || nodeConfig.Node.BatchPoster.ParentChainWallet.OnlyCreateKey { + l1TransactionOptsBatchPoster, dataSigner, err = util.OpenWallet("l1-batch-poster", &nodeConfig.Node.BatchPoster.ParentChainWallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID)) + if err != nil { + flag.Usage() + log.Crit("error opening Batch poster parent chain wallet", "path", nodeConfig.Node.BatchPoster.ParentChainWallet.Pathname, "account", nodeConfig.Node.BatchPoster.ParentChainWallet.Account, "err", err) } - } else { - if *l1Wallet != defaultL1WalletConfig { - log.Crit("--parent-chain.wallet cannot be set if either --node.staker.l1-wallet or --node.batch-poster.l1-wallet are set") + if nodeConfig.Node.BatchPoster.ParentChainWallet.OnlyCreateKey { + return 0 } - if sequencerNeedsKey || nodeConfig.Node.BatchPoster.ParentChainWallet.OnlyCreateKey { - l1TransactionOptsBatchPoster, dataSigner, err = util.OpenWallet("l1-batch-poster", &nodeConfig.Node.BatchPoster.ParentChainWallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID)) - if err != nil { - flag.Usage() - log.Crit("error opening Batch poster parent chain wallet", "path", nodeConfig.Node.BatchPoster.ParentChainWallet.Pathname, "account", nodeConfig.Node.BatchPoster.ParentChainWallet.Account, "err", err) - } - if nodeConfig.Node.BatchPoster.ParentChainWallet.OnlyCreateKey { - return 0 - } + } + if validatorNeedsKey || nodeConfig.Node.Staker.ParentChainWallet.OnlyCreateKey { + l1TransactionOptsValidator, _, err = util.OpenWallet("l1-validator", &nodeConfig.Node.Staker.ParentChainWallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID)) + if err != nil { + flag.Usage() + log.Crit("error opening Validator parent chain wallet", "path", nodeConfig.Node.Staker.ParentChainWallet.Pathname, "account", nodeConfig.Node.Staker.ParentChainWallet.Account, "err", err) } - if validatorNeedsKey || nodeConfig.Node.Staker.ParentChainWallet.OnlyCreateKey { - l1TransactionOptsValidator, _, err = util.OpenWallet("l1-validator", &nodeConfig.Node.Staker.ParentChainWallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID)) - if err != nil { - flag.Usage() - log.Crit("error opening Validator parent chain wallet", "path", nodeConfig.Node.Staker.ParentChainWallet.Pathname, "account", nodeConfig.Node.Staker.ParentChainWallet.Account, "err", err) - } - if nodeConfig.Node.Staker.ParentChainWallet.OnlyCreateKey { - return 0 - } + if nodeConfig.Node.Staker.ParentChainWallet.OnlyCreateKey { + return 0 } } @@ -317,14 +298,14 @@ func mainImpl() int { } } liveNodeConfig := genericconf.NewLiveConfig[*NodeConfig](args, nodeConfig, func(ctx context.Context, args []string) (*NodeConfig, error) { - nodeConfig, _, _, err := ParseNode(ctx, args) + nodeConfig, _, err := ParseNode(ctx, args) return nodeConfig, err }) var rollupAddrs chaininfo.RollupAddresses var l1Client *ethclient.Client var l1Reader *headerreader.HeaderReader - var blobReader arbstate.BlobReader + var blobReader daprovider.BlobReader if nodeConfig.Node.ParentChainReader.Enable { confFetcher := func() *rpcclient.ClientConfig { return &liveNodeConfig.Get().ParentChain.Connection } rpcClient := rpcclient.NewRpcClient(confFetcher, nil) @@ -452,7 +433,21 @@ func mainImpl() int { if len(allowedWasmModuleRoots) > 0 { moduleRootMatched := false for _, root := range allowedWasmModuleRoots { - if common.HexToHash(root) == moduleRoot { + bytes, err := hex.DecodeString(strings.TrimPrefix(root, "0x")) + if err == nil { + if common.HexToHash(root) == common.BytesToHash(bytes) { + moduleRootMatched = true + break + } + continue + } + locator, locatorErr := server_common.NewMachineLocator(root) + if locatorErr != nil { + log.Warn("allowed-wasm-module-roots: value not a hex nor valid path:", "value", root, "locatorErr", locatorErr, "decodeErr", err) + continue + } + path := locator.GetMachinePath(moduleRoot) + if _, err := os.Stat(path); err == nil { moduleRootMatched = true break } @@ -476,7 +471,7 @@ func mainImpl() int { } } - chainDb, l2BlockChain, err := openInitializeChainDb(ctx, stack, nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), l1Client, rollupAddrs) + chainDb, l2BlockChain, err := openInitializeChainDb(ctx, stack, nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), &nodeConfig.Persistent, l1Client, rollupAddrs) if l2BlockChain != nil { deferFuncs = append(deferFuncs, func() { l2BlockChain.Stop() }) } @@ -487,13 +482,33 @@ func mainImpl() int { return 1 } - arbDb, err := stack.OpenDatabase("arbitrumdata", 0, 0, "", false) + arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, nodeConfig.Persistent.Pebble.ExtraOptions("arbitrumdata")) deferFuncs = append(deferFuncs, func() { closeDb(arbDb, "arbDb") }) if err != nil { log.Error("failed to open database", "err", err) + log.Error("database is corrupt; delete it and try again", "database-directory", stack.InstanceDir()) return 1 } + fatalErrChan := make(chan error, 10) + + var blocksReExecutor *blocksreexecutor.BlocksReExecutor + if nodeConfig.BlocksReExecutor.Enable && l2BlockChain != nil { + blocksReExecutor = blocksreexecutor.New(&nodeConfig.BlocksReExecutor, l2BlockChain, fatalErrChan) + if nodeConfig.Init.ThenQuit { + success := make(chan struct{}) + blocksReExecutor.Start(ctx, success) + deferFuncs = append(deferFuncs, func() { blocksReExecutor.StopAndWait() }) + select { + case err := <-fatalErrChan: + log.Error("shutting down due to fatal error", "err", err) + defer log.Error("shut down due to fatal error", "err", err) + return 1 + case <-success: + } + } + } + if nodeConfig.Init.ThenQuit && nodeConfig.Init.ResetToMessage < 0 { return 0 } @@ -514,8 +529,6 @@ func mainImpl() int { return 1 } - fatalErrChan := make(chan error, 10) - var valNode *valnode.ValidationNode if sameProcessValidationNodeEnabled { valNode, err = valnode.CreateValidationNode( @@ -599,7 +612,7 @@ func mainImpl() int { } liveNodeConfig.SetOnReloadHook(func(oldCfg *NodeConfig, newCfg *NodeConfig) error { - if err := genericconf.InitLog(newCfg.LogType, log.Lvl(newCfg.LogLevel), &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)); err != nil { + if err := genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)); err != nil { return fmt.Errorf("failed to re-init logging: %w", err) } return currentNode.OnConfigReload(&oldCfg.Node, &newCfg.Node) @@ -644,9 +657,8 @@ func mainImpl() int { // remove previous deferFuncs, StopAndWait closes database and blockchain. deferFuncs = []func(){func() { currentNode.StopAndWait() }} } - if nodeConfig.BlocksReExecutor.Enable && l2BlockChain != nil { - blocksReExecutor := blocksreexecutor.New(&nodeConfig.BlocksReExecutor, l2BlockChain, fatalErrChan) - blocksReExecutor.Start(ctx) + if blocksReExecutor != nil && !nodeConfig.Init.ThenQuit { + blocksReExecutor.Start(ctx, nil) deferFuncs = append(deferFuncs, func() { blocksReExecutor.StopAndWait() }) } @@ -690,7 +702,7 @@ type NodeConfig struct { Validation valnode.Config `koanf:"validation" reload:"hot"` ParentChain conf.ParentChainConfig `koanf:"parent-chain" reload:"hot"` Chain conf.L2Config `koanf:"chain"` - LogLevel int `koanf:"log-level" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` LogType string `koanf:"log-type" reload:"hot"` FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` Persistent conf.PersistentConfig `koanf:"persistent"` @@ -716,7 +728,7 @@ var NodeConfigDefault = NodeConfig{ Validation: valnode.DefaultValidationConfig, ParentChain: conf.L1ConfigDefault, Chain: conf.L2ConfigDefault, - LogLevel: int(log.LvlInfo), + LogLevel: "INFO", LogType: "plaintext", FileLogging: genericconf.DefaultFileLoggingConfig, Persistent: conf.PersistentConfigDefault, @@ -742,7 +754,7 @@ func NodeConfigAddOptions(f *flag.FlagSet) { valnode.ValidationConfigAddOptions("validation", f) conf.L1ConfigAddOptions("parent-chain", f) conf.L2ConfigAddOptions("chain", f) - f.Int("log-level", NodeConfigDefault.LogLevel, "log level") + f.String("log-level", NodeConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") f.String("log-type", NodeConfigDefault.LogType, "log type (plaintext or json)") genericconf.FileLoggingConfigAddOptions("file-logging", f) conf.PersistentConfigAddOptions("persistent", f) @@ -767,7 +779,6 @@ func (c *NodeConfig) ResolveDirectoryNames() error { if err != nil { return err } - c.ParentChain.ResolveDirectoryNames(c.Persistent.Chain) c.Chain.ResolveDirectoryNames(c.Persistent.Chain) return nil @@ -837,14 +848,14 @@ func (c *NodeConfig) GetReloadInterval() time.Duration { return c.Conf.ReloadInterval } -func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.WalletConfig, *genericconf.WalletConfig, error) { +func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.WalletConfig, error) { f := flag.NewFlagSet("", flag.ContinueOnError) NodeConfigAddOptions(f) k, err := confighelpers.BeginCommonParse(f, args) if err != nil { - return nil, nil, nil, err + return nil, nil, err } l2ChainId := k.Int64("chain.id") @@ -855,17 +866,17 @@ func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.Wa l2ChainInfoJson := k.String("chain.info-json") err = applyChainParameters(ctx, k, uint64(l2ChainId), l2ChainName, l2ChainInfoFiles, l2ChainInfoJson, l2ChainInfoIpfsUrl, l2ChainInfoIpfsDownloadPath) if err != nil { - return nil, nil, nil, err + return nil, nil, err } err = confighelpers.ApplyOverrides(f, k) if err != nil { - return nil, nil, nil, err + return nil, nil, err } var nodeConfig NodeConfig if err := confighelpers.EndCommonParse(k, &nodeConfig); err != nil { - return nil, nil, nil, err + return nil, nil, err } // Don't print wallet passwords @@ -877,23 +888,21 @@ func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.Wa "chain.dev-wallet.private-key": "", }) if err != nil { - return nil, nil, nil, err + return nil, nil, err } } if nodeConfig.Persistent.Chain == "" { - return nil, nil, nil, errors.New("--persistent.chain not specified") + return nil, nil, errors.New("--persistent.chain not specified") } err = nodeConfig.ResolveDirectoryNames() if err != nil { - return nil, nil, nil, err + return nil, nil, err } // Don't pass around wallet contents with normal configuration - l1Wallet := nodeConfig.ParentChain.Wallet l2DevWallet := nodeConfig.Chain.DevWallet - nodeConfig.ParentChain.Wallet = genericconf.WalletConfigDefault nodeConfig.Chain.DevWallet = genericconf.WalletConfigDefault if nodeConfig.Execution.Caching.Archive { @@ -901,9 +910,9 @@ func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.Wa } err = nodeConfig.Validate() if err != nil { - return nil, nil, nil, err + return nil, nil, err } - return &nodeConfig, &l1Wallet, &l2DevWallet, nil + return &nodeConfig, &l2DevWallet, nil } func aggregateL2ChainInfoFiles(ctx context.Context, l2ChainInfoFiles []string, l2ChainInfoIpfsUrl string, l2ChainInfoIpfsDownloadPath string) []string { @@ -935,6 +944,7 @@ func applyChainParameters(ctx context.Context, k *koanf.Koanf, chainId uint64, c } chainDefaults := map[string]interface{}{ "persistent.chain": chainInfo.ChainName, + "chain.name": chainInfo.ChainName, "chain.id": chainInfo.ChainConfig.ChainID.Uint64(), "parent-chain.id": chainInfo.ParentChainId, } diff --git a/cmd/pruning/pruning.go b/cmd/pruning/pruning.go index e9f593eb8..ab6ec8094 100644 --- a/cmd/pruning/pruning.go +++ b/cmd/pruning/pruning.go @@ -80,12 +80,12 @@ func (r *importantRoots) addHeader(header *types.Header, overwrite bool) error { var hashListRegex = regexp.MustCompile("^(0x)?[0-9a-fA-F]{64}(,(0x)?[0-9a-fA-F]{64})*$") // Finds important roots to retain while proving -func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) ([]common.Hash, error) { +func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) ([]common.Hash, error) { chainConfig := gethexec.TryReadStoredChainConfig(chainDb) if chainConfig == nil { return nil, errors.New("database doesn't have a chain config (was this node initialized?)") } - arbDb, err := stack.OpenDatabase("arbitrumdata", 0, 0, "", true) + arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", true, persistentConfig.Pebble.ExtraOptions("arbitrumdata")) if err != nil { return nil, err } @@ -189,7 +189,7 @@ func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node return nil, fmt.Errorf("failed to get finalized block: %w", err) } l1BlockNum := l1Block.NumberU64() - tracker, err := arbnode.NewInboxTracker(arbDb, nil, nil, nil, nil) + tracker, err := arbnode.NewInboxTracker(arbDb, nil, nil, arbnode.DefaultSnapSyncConfig) if err != nil { return nil, err } @@ -232,16 +232,16 @@ func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node return roots.roots, nil } -func PruneChainDb(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) error { +func PruneChainDb(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) error { if initConfig.Prune == "" { - return pruner.RecoverPruning(stack.InstanceDir(), chainDb) + return pruner.RecoverPruning(stack.InstanceDir(), chainDb, initConfig.PruneThreads) } - root, err := findImportantRoots(ctx, chainDb, stack, initConfig, cacheConfig, l1Client, rollupAddrs, validatorRequired) + root, err := findImportantRoots(ctx, chainDb, stack, initConfig, cacheConfig, persistentConfig, l1Client, rollupAddrs, validatorRequired) if err != nil { return fmt.Errorf("failed to find root to retain for pruning: %w", err) } - pruner, err := pruner.NewPruner(chainDb, pruner.Config{Datadir: stack.InstanceDir(), BloomSize: initConfig.PruneBloomSize}) + pruner, err := pruner.NewPruner(chainDb, pruner.Config{Datadir: stack.InstanceDir(), BloomSize: initConfig.PruneBloomSize, Threads: initConfig.PruneThreads, CleanCacheSize: initConfig.PruneTrieCleanCache}) if err != nil { return err } diff --git a/cmd/relay/relay.go b/cmd/relay/relay.go index 40f4f26ee..6f786f976 100644 --- a/cmd/relay/relay.go +++ b/cmd/relay/relay.go @@ -6,6 +6,7 @@ package main import ( "context" "fmt" + "io" "os" "os/signal" "syscall" @@ -62,14 +63,18 @@ func startup() error { confighelpers.PrintErrorAndExit(err, printSampleUsage) } - logFormat, err := genericconf.ParseLogType(relayConfig.LogType) + handler, err := genericconf.HandlerFromLogType(relayConfig.LogType, io.Writer(os.Stderr)) if err != nil { flag.Usage() - panic(fmt.Sprintf("Error parsing log type: %v", err)) + return fmt.Errorf("error parsing log type when creating handler: %w", err) } - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, logFormat)) - glogger.Verbosity(log.Lvl(relayConfig.LogLevel)) - log.Root().SetHandler(glogger) + logLevel, err := genericconf.ToSlogLevel(relayConfig.LogLevel) + if err != nil { + confighelpers.PrintErrorAndExit(err, printSampleUsage) + } + glogger := log.NewGlogHandler(handler) + glogger.Verbosity(logLevel) + log.SetDefault(log.NewLogger(glogger)) vcsRevision, _, vcsTime := confighelpers.GetVersion() log.Info("Running Arbitrum nitro relay", "revision", vcsRevision, "vcs.time", vcsTime) diff --git a/cmd/replay/main.go b/cmd/replay/main.go index 12fd92190..554c91632 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -10,6 +10,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" "os" "github.com/ethereum/go-ethereum/common" @@ -28,6 +29,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/das/dastree" @@ -117,8 +119,8 @@ func (dasReader *PreimageDASReader) HealthCheck(ctx context.Context) error { return nil } -func (dasReader *PreimageDASReader) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { - return arbstate.DiscardImmediately, nil +func (dasReader *PreimageDASReader) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { + return daprovider.DiscardImmediately, nil } type BlobPreimageReader struct{} @@ -201,9 +203,10 @@ func main() { wavmio.StubInit() gethhook.RequireHookedGeth() - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.LvlError) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(log.LevelError) + log.SetDefault(log.NewLogger(glogger)) populateEcdsaCaches() @@ -230,29 +233,23 @@ func main() { if lastBlockHeader != nil { delayedMessagesRead = lastBlockHeader.Nonce.Uint64() } - - var dasReader *PreimageDASReader - var eigenDAReader *PreimageEigenDAReader + var dasReader daprovider.DASReader if dasEnabled { dasReader = &PreimageDASReader{} } else if eigenDAEnabled { eigenDAReader = &PreimageEigenDAReader{} } backend := WavmInbox{} - var keysetValidationMode = arbstate.KeysetPanicIfInvalid + var keysetValidationMode = daprovider.KeysetPanicIfInvalid if backend.GetPositionWithinMessage() > 0 { - keysetValidationMode = arbstate.KeysetDontValidate + keysetValidationMode = daprovider.KeysetDontValidate } - var daProviders []arbstate.DataAvailabilityProvider - + var dapReaders []daprovider.Reader if dasReader != nil { - daProviders = append(daProviders, arbstate.NewDAProviderDAS(dasReader)) - } - if eigenDAReader != nil { - daProviders = append(daProviders, arbstate.NewDAProviderEigenDA(eigenDAReader)) + dapReaders = append(dapReaders, daprovider.NewReaderForDAS(dasReader)) } - daProviders = append(daProviders, arbstate.NewDAProviderBlobReader(&BlobPreimageReader{})) - inboxMultiplexer := arbstate.NewInboxMultiplexer(backend, delayedMessagesRead, daProviders, keysetValidationMode) + dapReaders = append(dapReaders, daprovider.NewReaderForBlobReader(&BlobPreimageReader{})) + inboxMultiplexer := arbstate.NewInboxMultiplexer(backend, delayedMessagesRead, dapReaders, keysetValidationMode) ctx := context.Background() message, err := inboxMultiplexer.Pop(ctx) if err != nil { @@ -310,7 +307,7 @@ func main() { batchFetcher := func(batchNum uint64) ([]byte, error) { return wavmio.ReadInboxMessage(batchNum), nil } - newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher) + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher, false) if err != nil { panic(err) } diff --git a/cmd/staterecovery/staterecovery.go b/cmd/staterecovery/staterecovery.go index 6390826a9..58ad06ad1 100644 --- a/cmd/staterecovery/staterecovery.go +++ b/cmd/staterecovery/staterecovery.go @@ -31,7 +31,7 @@ func RecreateMissingStates(chainDb ethdb.Database, bc *core.BlockChain, cacheCon return fmt.Errorf("start block parent is missing, parent block number: %d", current-1) } hashConfig := *hashdb.Defaults - hashConfig.CleanCacheSize = cacheConfig.TrieCleanLimit + hashConfig.CleanCacheSize = cacheConfig.TrieCleanLimit * 1024 * 1024 trieConfig := &trie.Config{ Preimages: false, HashDB: &hashConfig, diff --git a/cmd/util/confighelpers/configuration.go b/cmd/util/confighelpers/configuration.go index 3ff27d65c..ff33da673 100644 --- a/cmd/util/confighelpers/configuration.go +++ b/cmd/util/confighelpers/configuration.go @@ -92,14 +92,49 @@ func applyOverrideOverrides(f *flag.FlagSet, k *koanf.Koanf) error { return nil } +var envvarsToSplitOnComma map[string]any = map[string]any{ + "auth.api": struct{}{}, + "auth.origins": struct{}{}, + "chain.info-files": struct{}{}, + "conf.file": struct{}{}, + "execution.secondary-forwarding-target": struct{}{}, + "graphql.corsdomain": struct{}{}, + "graphql.vhosts": struct{}{}, + "http.api": struct{}{}, + "http.corsdomain": struct{}{}, + "http.vhosts": struct{}{}, + "node.data-availability.rest-aggregator.urls": struct{}{}, + "node.feed.input.secondary-url": struct{}{}, + "node.feed.input.url": struct{}{}, + "node.feed.input.verify.allowed-addresses": struct{}{}, + "node.seq-coordinator.signer.ecdsa.allowed-addresses": struct{}{}, + "p2p.bootnodes": struct{}{}, + "p2p.bootnodes-v5": struct{}{}, + "validation.api-auth": struct{}{}, + "validation.arbitrator.redis-validation-server-config.module-roots": struct{}{}, + "validation.wasm.allowed-wasm-module-roots": struct{}{}, + "ws.api": struct{}{}, + "ws.origins": struct{}{}, +} + func loadEnvironmentVariables(k *koanf.Koanf) error { envPrefix := k.String("conf.env-prefix") if len(envPrefix) != 0 { - return k.Load(env.Provider(envPrefix+"_", ".", func(s string) string { + return k.Load(env.ProviderWithValue(envPrefix+"_", ".", func(key string, v string) (string, interface{}) { // FOO__BAR -> foo-bar to handle dash in config names - s = strings.ReplaceAll(strings.ToLower( - strings.TrimPrefix(s, envPrefix+"_")), "__", "-") - return strings.ReplaceAll(s, "_", ".") + key = strings.ReplaceAll(strings.ToLower( + strings.TrimPrefix(key, envPrefix+"_")), "__", "-") + key = strings.ReplaceAll(key, "_", ".") + + if _, found := envvarsToSplitOnComma[key]; found { + // If there are commas in the value, split the value into a slice. + if strings.Contains(v, ",") { + return key, strings.Split(v, ",") + + } + } + + return key, v }), nil) } diff --git a/contracts b/contracts deleted file mode 160000 index 2a561f885..000000000 --- a/contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2a561f88513b1473bd333dbc88f9b11c21df35be diff --git a/das/aggregator.go b/das/aggregator.go index 4b4571eb4..f82174fb1 100644 --- a/das/aggregator.go +++ b/das/aggregator.go @@ -17,32 +17,32 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/solgen/go/bridgegen" - "github.com/offchainlabs/nitro/util/contracts" "github.com/offchainlabs/nitro/util/pretty" ) type AggregatorConfig struct { - Enable bool `koanf:"enable"` - AssumedHonest int `koanf:"assumed-honest"` - Backends string `koanf:"backends"` + Enable bool `koanf:"enable"` + AssumedHonest int `koanf:"assumed-honest"` + Backends string `koanf:"backends"` + MaxStoreChunkBodySize int `koanf:"max-store-chunk-body-size"` } var DefaultAggregatorConfig = AggregatorConfig{ - AssumedHonest: 0, - Backends: "", + AssumedHonest: 0, + Backends: "", + MaxStoreChunkBodySize: 512 * 1024, } -var BatchToDasFailed = errors.New("unable to batch to DAS") - func AggregatorConfigAddOptions(prefix string, f *flag.FlagSet) { - f.Bool(prefix+".enable", DefaultAggregatorConfig.Enable, "enable storage/retrieval of sequencer batch data from a list of RPC endpoints; this should only be used by the batch poster and not in combination with other DAS storage types") + f.Bool(prefix+".enable", DefaultAggregatorConfig.Enable, "enable storage of sequencer batch data from a list of RPC endpoints; this should only be used by the batch poster and not in combination with other DAS storage types") f.Int(prefix+".assumed-honest", DefaultAggregatorConfig.AssumedHonest, "Number of assumed honest backends (H). If there are N backends, K=N+1-H valid responses are required to consider an Store request to be successful.") f.String(prefix+".backends", DefaultAggregatorConfig.Backends, "JSON RPC backend configuration") + f.Int(prefix+".max-store-chunk-body-size", DefaultAggregatorConfig.MaxStoreChunkBodySize, "maximum HTTP POST body size to use for individual batch chunks, including JSON RPC overhead and an estimated overhead of 512B of headers") } type Aggregator struct { @@ -55,7 +55,6 @@ type Aggregator struct { maxAllowedServiceStoreFailures int keysetHash [32]byte keysetBytes []byte - addrVerifier *contracts.AddressVerifier } type ServiceDetails struct { @@ -123,11 +122,6 @@ func NewAggregatorWithSeqInboxCaller( return nil, err } - var addrVerifier *contracts.AddressVerifier - if seqInboxCaller != nil { - addrVerifier = contracts.NewAddressVerifier(seqInboxCaller) - } - return &Aggregator{ config: config.RPCAggregator, services: services, @@ -136,7 +130,6 @@ func NewAggregatorWithSeqInboxCaller( maxAllowedServiceStoreFailures: config.RPCAggregator.AssumedHonest - 1, keysetHash: keysetHash, keysetBytes: keysetBytes, - addrVerifier: addrVerifier, }, nil } @@ -159,27 +152,8 @@ type storeResponse struct { // // If Store gets not enough successful responses by the time its context is canceled // (eg via TimeoutWrapper) then it also returns an error. -// -// If Sequencer Inbox contract details are provided when a das.Aggregator is -// constructed, calls to Store(...) will try to verify the passed-in data's signature -// is from the batch poster. If the contract details are not provided, then the -// signature is not checked, which is useful for testing. -func (a *Aggregator) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) { - log.Trace("das.Aggregator.Store", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0), "sig", pretty.FirstFewBytes(sig)) - if a.addrVerifier != nil { - actualSigner, err := DasRecoverSigner(message, timeout, sig) - if err != nil { - return nil, err - } - isBatchPosterOrSequencer, err := a.addrVerifier.IsBatchPosterOrSequencer(ctx, actualSigner) - if err != nil { - return nil, err - } - if !isBatchPosterOrSequencer { - return nil, errors.New("store request not properly signed") - } - } - +func (a *Aggregator) Store(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) { + log.Trace("das.Aggregator.Store", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0)) responses := make(chan storeResponse, len(a.services)) expectedHash := dastree.Hash(message) @@ -194,7 +168,7 @@ func (a *Aggregator) Store(ctx context.Context, message []byte, timeout uint64, metrics.GetOrRegisterCounter(metricBase+"/error/all/total", nil).Inc(1) } - cert, err := d.service.Store(storeCtx, message, timeout, sig) + cert, err := d.service.Store(storeCtx, message, timeout) if err != nil { incFailureMetric() if errors.Is(err, context.DeadlineExceeded) { @@ -243,7 +217,7 @@ func (a *Aggregator) Store(ctx context.Context, message []byte, timeout uint64, }(ctx, d) } - var aggCert arbstate.DataAvailabilityCertificate + var aggCert daprovider.DataAvailabilityCertificate type certDetails struct { pubKeys []blsSignatures.PublicKey @@ -296,7 +270,7 @@ func (a *Aggregator) Store(ctx context.Context, message []byte, timeout uint64, } } else if storeFailures > a.maxAllowedServiceStoreFailures { cd := certDetails{} - cd.err = fmt.Errorf("aggregator failed to store message to at least %d out of %d DASes (assuming %d are honest). %w", a.requiredServicesForStore, len(a.services), a.config.AssumedHonest, BatchToDasFailed) + cd.err = fmt.Errorf("aggregator failed to store message to at least %d out of %d DASes (assuming %d are honest). %w", a.requiredServicesForStore, len(a.services), a.config.AssumedHonest, daprovider.ErrBatchToDasFailed) certDetailsChan <- cd returned = true } @@ -323,10 +297,10 @@ func (a *Aggregator) Store(ctx context.Context, message []byte, timeout uint64, verified, err := blsSignatures.VerifySignature(aggCert.Sig, aggCert.SerializeSignableFields(), aggPubKey) if err != nil { //nolint:errorlint - return nil, fmt.Errorf("%s. %w", err.Error(), BatchToDasFailed) + return nil, fmt.Errorf("%s. %w", err.Error(), daprovider.ErrBatchToDasFailed) } if !verified { - return nil, fmt.Errorf("failed aggregate signature check. %w", BatchToDasFailed) + return nil, fmt.Errorf("failed aggregate signature check. %w", daprovider.ErrBatchToDasFailed) } return &aggCert, nil } diff --git a/das/aggregator_test.go b/das/aggregator_test.go index 776af3975..4bc209513 100644 --- a/das/aggregator_test.go +++ b/das/aggregator_test.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "io" "math/rand" "os" "strconv" @@ -15,10 +16,10 @@ import ( "testing" "time" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/blsSignatures" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" ) func TestDAS_BasicAggregationLocal(t *testing.T) { @@ -53,7 +54,7 @@ func TestDAS_BasicAggregationLocal(t *testing.T) { Require(t, err) rawMsg := []byte("It's time for you to see the fnords.") - cert, err := aggregator.Store(ctx, rawMsg, 0, []byte{}) + cert, err := aggregator.Store(ctx, rawMsg, 0) Require(t, err, "Error storing message") for _, storageService := range storageServices { @@ -122,17 +123,17 @@ type WrapStore struct { DataAvailabilityServiceWriter } -func (w *WrapStore) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) { +func (w *WrapStore) Store(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) { switch w.injector.shouldFail() { case success: - return w.DataAvailabilityServiceWriter.Store(ctx, message, timeout, sig) + return w.DataAvailabilityServiceWriter.Store(ctx, message, timeout) case immediateError: return nil, errors.New("expected Store failure") case tooSlow: <-ctx.Done() return nil, ctx.Err() case dataCorruption: - cert, err := w.DataAvailabilityServiceWriter.Store(ctx, message, timeout, sig) + cert, err := w.DataAvailabilityServiceWriter.Store(ctx, message, timeout) if err != nil { return nil, err } @@ -158,9 +159,10 @@ func min(a, b int) int { } func enableLogging() { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.LvlTrace) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(log.LevelTrace) + log.SetDefault(log.NewLogger(glogger)) } func testConfigurableStorageFailures(t *testing.T, shouldFailAggregation bool) { @@ -212,7 +214,7 @@ func testConfigurableStorageFailures(t *testing.T, shouldFailAggregation bool) { Require(t, err) rawMsg := []byte("It's time for you to see the fnords.") - cert, err := aggregator.Store(ctx, rawMsg, 0, []byte{}) + cert, err := aggregator.Store(ctx, rawMsg, 0) if !shouldFailAggregation { Require(t, err, "Error storing message") } else { diff --git a/das/cache_storage_service.go b/das/cache_storage_service.go index 13bdb189d..439ccda08 100644 --- a/das/cache_storage_service.go +++ b/das/cache_storage_service.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" flag "github.com/spf13/pflag" @@ -82,7 +82,7 @@ func (c *CacheStorageService) Close(ctx context.Context) error { return c.baseStorageService.Close(ctx) } -func (c *CacheStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (c *CacheStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { return c.baseStorageService.ExpirationPolicy(ctx) } diff --git a/das/chain_fetch_das.go b/das/chain_fetch_das.go index bc8ab5bc1..99311deca 100644 --- a/das/chain_fetch_das.go +++ b/das/chain_fetch_das.go @@ -8,7 +8,7 @@ import ( "errors" "sync" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/util/pretty" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -38,13 +38,13 @@ func (c *syncedKeysetCache) put(key [32]byte, value []byte) { } type ChainFetchReader struct { - arbstate.DataAvailabilityReader + daprovider.DASReader seqInboxCaller *bridgegen.SequencerInboxCaller seqInboxFilterer *bridgegen.SequencerInboxFilterer keysetCache syncedKeysetCache } -func NewChainFetchReader(inner arbstate.DataAvailabilityReader, l1client arbutil.L1Interface, seqInboxAddr common.Address) (*ChainFetchReader, error) { +func NewChainFetchReader(inner daprovider.DASReader, l1client arbutil.L1Interface, seqInboxAddr common.Address) (*ChainFetchReader, error) { seqInbox, err := bridgegen.NewSequencerInbox(seqInboxAddr, l1client) if err != nil { return nil, err @@ -53,18 +53,18 @@ func NewChainFetchReader(inner arbstate.DataAvailabilityReader, l1client arbutil return NewChainFetchReaderWithSeqInbox(inner, seqInbox) } -func NewChainFetchReaderWithSeqInbox(inner arbstate.DataAvailabilityReader, seqInbox *bridgegen.SequencerInbox) (*ChainFetchReader, error) { +func NewChainFetchReaderWithSeqInbox(inner daprovider.DASReader, seqInbox *bridgegen.SequencerInbox) (*ChainFetchReader, error) { return &ChainFetchReader{ - DataAvailabilityReader: inner, - seqInboxCaller: &seqInbox.SequencerInboxCaller, - seqInboxFilterer: &seqInbox.SequencerInboxFilterer, - keysetCache: syncedKeysetCache{cache: make(map[[32]byte][]byte)}, + DASReader: inner, + seqInboxCaller: &seqInbox.SequencerInboxCaller, + seqInboxFilterer: &seqInbox.SequencerInboxFilterer, + keysetCache: syncedKeysetCache{cache: make(map[[32]byte][]byte)}, }, nil } func (c *ChainFetchReader) GetByHash(ctx context.Context, hash common.Hash) ([]byte, error) { log.Trace("das.ChainFetchReader.GetByHash", "hash", pretty.PrettyHash(hash)) - return chainFetchGetByHash(ctx, c.DataAvailabilityReader, &c.keysetCache, c.seqInboxCaller, c.seqInboxFilterer, hash) + return chainFetchGetByHash(ctx, c.DASReader, &c.keysetCache, c.seqInboxCaller, c.seqInboxFilterer, hash) } func (c *ChainFetchReader) String() string { return "ChainFetchReader" @@ -72,7 +72,7 @@ func (c *ChainFetchReader) String() string { func chainFetchGetByHash( ctx context.Context, - daReader arbstate.DataAvailabilityReader, + daReader daprovider.DASReader, cache *syncedKeysetCache, seqInboxCaller *bridgegen.SequencerInboxCaller, seqInboxFilterer *bridgegen.SequencerInboxFilterer, diff --git a/das/das.go b/das/das.go index dd8e43a34..5528323a9 100644 --- a/das/das.go +++ b/das/das.go @@ -5,7 +5,6 @@ package das import ( "context" - "encoding/binary" "errors" "fmt" "math" @@ -16,18 +15,17 @@ import ( "github.com/ethereum/go-ethereum/log" flag "github.com/spf13/pflag" - "github.com/offchainlabs/nitro/arbstate" - "github.com/offchainlabs/nitro/blsSignatures" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) type DataAvailabilityServiceWriter interface { // Store requests that the message be stored until timeout (UTC time in unix epoch seconds). - Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) + Store(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) fmt.Stringer } type DataAvailabilityServiceReader interface { - arbstate.DataAvailabilityReader + daprovider.DASReader fmt.Stringer } @@ -43,11 +41,9 @@ type DataAvailabilityConfig struct { LocalCache CacheConfig `koanf:"local-cache"` RedisCache RedisConfig `koanf:"redis-cache"` - LocalDBStorage LocalDBStorageConfig `koanf:"local-db-storage"` - LocalFileStorage LocalFileStorageConfig `koanf:"local-file-storage"` - S3Storage S3StorageServiceConfig `koanf:"s3-storage"` - IpfsStorage IpfsStorageServiceConfig `koanf:"ipfs-storage"` - RegularSyncStorage RegularSyncStorageConfig `koanf:"regular-sync-storage"` + LocalDBStorage LocalDBStorageConfig `koanf:"local-db-storage"` + LocalFileStorage LocalFileStorageConfig `koanf:"local-file-storage"` + S3Storage S3StorageServiceConfig `koanf:"s3-storage"` Key KeyConfig `koanf:"key"` @@ -67,9 +63,9 @@ var DefaultDataAvailabilityConfig = DataAvailabilityConfig{ RequestTimeout: 5 * time.Second, Enable: false, RestAggregator: DefaultRestfulClientAggregatorConfig, + RPCAggregator: DefaultAggregatorConfig, ParentChainConnectionAttempts: 15, PanicOnError: false, - IpfsStorage: DefaultIpfsStorageServiceConfig, } func OptionalAddressFromString(s string) (*common.Address, error) { @@ -116,7 +112,6 @@ func dataAvailabilityConfigAddOptions(prefix string, f *flag.FlagSet, r role) { LocalDBStorageConfigAddOptions(prefix+".local-db-storage", f) LocalFileStorageConfigAddOptions(prefix+".local-file-storage", f) S3ConfigAddOptions(prefix+".s3-storage", f) - RegularSyncStorageConfigAddOptions(prefix+".regular-sync-storage", f) // Key config for storage KeyConfigAddOptions(prefix+".key", f) @@ -130,7 +125,6 @@ func dataAvailabilityConfigAddOptions(prefix string, f *flag.FlagSet, r role) { } // Both the Nitro node and daserver can use these options. - IpfsStorageServiceConfigAddOptions(prefix+".ipfs-storage", f) RestfulClientAggregatorConfigAddOptions(prefix+".rest-aggregator", f) f.String(prefix+".parent-chain-node-url", DefaultDataAvailabilityConfig.ParentChainNodeURL, "URL for parent chain node, only used in standalone daserver; when running as part of a node that node's L1 configuration is used") @@ -138,25 +132,6 @@ func dataAvailabilityConfigAddOptions(prefix string, f *flag.FlagSet, r role) { f.String(prefix+".sequencer-inbox-address", DefaultDataAvailabilityConfig.SequencerInboxAddress, "parent chain address of SequencerInbox contract") } -func Serialize(c *arbstate.DataAvailabilityCertificate) []byte { - - flags := arbstate.DASMessageHeaderFlag - if c.Version != 0 { - flags |= arbstate.TreeDASMessageHeaderFlag - } - - buf := make([]byte, 0) - buf = append(buf, flags) - buf = append(buf, c.KeysetHash[:]...) - buf = append(buf, c.SerializeSignableFields()...) - - var intData [8]byte - binary.BigEndian.PutUint64(intData[:], c.SignersMask) - buf = append(buf, intData[:]...) - - return append(buf, blsSignatures.SignatureToBytes(c.Sig)...) -} - func GetL1Client(ctx context.Context, maxConnectionAttempts int, l1URL string) (*ethclient.Client, error) { if maxConnectionAttempts <= 0 { maxConnectionAttempts = math.MaxInt diff --git a/das/dasRpcClient.go b/das/dasRpcClient.go index 54d8eba94..ca2ee8e7d 100644 --- a/das/dasRpcClient.go +++ b/das/dasRpcClient.go @@ -6,36 +6,145 @@ package das import ( "context" "fmt" + "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" + "golang.org/x/sync/errgroup" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/util/pretty" + "github.com/offchainlabs/nitro/util/signature" ) type DASRPCClient struct { // implements DataAvailabilityService - clnt *rpc.Client - url string + clnt *rpc.Client + url string + signer signature.DataSignerFunc + chunkSize uint64 } -func NewDASRPCClient(target string) (*DASRPCClient, error) { +func nilSigner(_ []byte) ([]byte, error) { + return []byte{}, nil +} + +const sendChunkJSONBoilerplate = "{\"jsonrpc\":\"2.0\",\"id\":4294967295,\"method\":\"das_sendChunked\",\"params\":[\"\"]}" + +func NewDASRPCClient(target string, signer signature.DataSignerFunc, maxStoreChunkBodySize int) (*DASRPCClient, error) { clnt, err := rpc.Dial(target) if err != nil { return nil, err } + if signer == nil { + signer = nilSigner + } + + // Byte arrays are encoded in base64 + chunkSize := (maxStoreChunkBodySize - len(sendChunkJSONBoilerplate) - 512 /* headers */) / 2 + if chunkSize <= 0 { + return nil, fmt.Errorf("max-store-chunk-body-size %d doesn't leave enough room for chunk payload", maxStoreChunkBodySize) + } + return &DASRPCClient{ - clnt: clnt, - url: target, + clnt: clnt, + url: target, + signer: signer, + chunkSize: uint64(chunkSize), }, nil } -func (c *DASRPCClient) Store(ctx context.Context, message []byte, timeout uint64, reqSig []byte) (*arbstate.DataAvailabilityCertificate, error) { - log.Trace("das.DASRPCClient.Store(...)", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0), "sig", pretty.FirstFewBytes(reqSig), "this", *c) +func (c *DASRPCClient) Store(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) { + timestamp := uint64(time.Now().Unix()) + nChunks := uint64(len(message)) / c.chunkSize + lastChunkSize := uint64(len(message)) % c.chunkSize + if lastChunkSize > 0 { + nChunks++ + } else { + lastChunkSize = c.chunkSize + } + totalSize := uint64(len(message)) + + startReqSig, err := applyDasSigner(c.signer, []byte{}, timestamp, nChunks, c.chunkSize, totalSize, timeout) + if err != nil { + return nil, err + } + + var startChunkedStoreResult StartChunkedStoreResult + if err := c.clnt.CallContext(ctx, &startChunkedStoreResult, "das_startChunkedStore", hexutil.Uint64(timestamp), hexutil.Uint64(nChunks), hexutil.Uint64(c.chunkSize), hexutil.Uint64(totalSize), hexutil.Uint64(timeout), hexutil.Bytes(startReqSig)); err != nil { + if strings.Contains(err.Error(), "the method das_startChunkedStore does not exist") { + return c.legacyStore(ctx, message, timeout) + } + return nil, err + } + batchId := uint64(startChunkedStoreResult.BatchId) + + g := new(errgroup.Group) + for i := uint64(0); i < nChunks; i++ { + var chunk []byte + if i == nChunks-1 { + chunk = message[i*c.chunkSize : i*c.chunkSize+lastChunkSize] + } else { + chunk = message[i*c.chunkSize : (i+1)*c.chunkSize] + } + + inner := func(_i uint64, _chunk []byte) func() error { + return func() error { return c.sendChunk(ctx, batchId, _i, _chunk) } + } + g.Go(inner(i, chunk)) + } + if err := g.Wait(); err != nil { + return nil, err + } + + finalReqSig, err := applyDasSigner(c.signer, []byte{}, uint64(startChunkedStoreResult.BatchId)) + if err != nil { + return nil, err + } + + var storeResult StoreResult + if err := c.clnt.CallContext(ctx, &storeResult, "das_commitChunkedStore", startChunkedStoreResult.BatchId, hexutil.Bytes(finalReqSig)); err != nil { + return nil, err + } + + respSig, err := blsSignatures.SignatureFromBytes(storeResult.Sig) + if err != nil { + return nil, err + } + + return &daprovider.DataAvailabilityCertificate{ + DataHash: common.BytesToHash(storeResult.DataHash), + Timeout: uint64(storeResult.Timeout), + SignersMask: uint64(storeResult.SignersMask), + Sig: respSig, + KeysetHash: common.BytesToHash(storeResult.KeysetHash), + Version: byte(storeResult.Version), + }, nil +} + +func (c *DASRPCClient) sendChunk(ctx context.Context, batchId, i uint64, chunk []byte) error { + chunkReqSig, err := applyDasSigner(c.signer, chunk, batchId, i) + if err != nil { + return err + } + + if err := c.clnt.CallContext(ctx, nil, "das_sendChunk", hexutil.Uint64(batchId), hexutil.Uint64(i), hexutil.Bytes(chunk), hexutil.Bytes(chunkReqSig)); err != nil { + return err + } + return nil +} + +func (c *DASRPCClient) legacyStore(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) { + log.Trace("das.DASRPCClient.Store(...)", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0), "this", *c) + + reqSig, err := applyDasSigner(c.signer, message, timeout) + if err != nil { + return nil, err + } + var ret StoreResult if err := c.clnt.CallContext(ctx, &ret, "das_store", hexutil.Bytes(message), hexutil.Uint64(timeout), hexutil.Bytes(reqSig)); err != nil { return nil, err @@ -44,7 +153,7 @@ func (c *DASRPCClient) Store(ctx context.Context, message []byte, timeout uint64 if err != nil { return nil, err } - return &arbstate.DataAvailabilityCertificate{ + return &daprovider.DataAvailabilityCertificate{ DataHash: common.BytesToHash(ret.DataHash), Timeout: uint64(ret.Timeout), SignersMask: uint64(ret.SignersMask), @@ -62,11 +171,11 @@ func (c *DASRPCClient) HealthCheck(ctx context.Context) error { return c.clnt.CallContext(ctx, nil, "das_healthCheck") } -func (c *DASRPCClient) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (c *DASRPCClient) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { var res string err := c.clnt.CallContext(ctx, &res, "das_expirationPolicy") if err != nil { return -1, err } - return arbstate.StringToExpirationPolicy(res) + return daprovider.StringToExpirationPolicy(res) } diff --git a/das/dasRpcServer.go b/das/dasRpcServer.go index 2f1fc1fd4..9e6228ca5 100644 --- a/das/dasRpcServer.go +++ b/das/dasRpcServer.go @@ -7,8 +7,11 @@ import ( "context" "errors" "fmt" + "math/rand" "net" "net/http" + "sync" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common/hexutil" @@ -28,31 +31,47 @@ var ( rpcStoreFailureGauge = metrics.NewRegisteredGauge("arb/das/rpc/store/failure", nil) rpcStoreStoredBytesGauge = metrics.NewRegisteredGauge("arb/das/rpc/store/bytes", nil) rpcStoreDurationHistogram = metrics.NewRegisteredHistogram("arb/das/rpc/store/duration", nil, metrics.NewBoundedHistogramSample()) + + rpcSendChunkSuccessGauge = metrics.NewRegisteredGauge("arb/das/rpc/sendchunk/success", nil) + rpcSendChunkFailureGauge = metrics.NewRegisteredGauge("arb/das/rpc/sendchunk/failure", nil) ) type DASRPCServer struct { daReader DataAvailabilityServiceReader daWriter DataAvailabilityServiceWriter daHealthChecker DataAvailabilityServiceHealthChecker + + signatureVerifier *SignatureVerifier + + batches *batchBuilder } -func StartDASRPCServer(ctx context.Context, addr string, portNum uint64, rpcServerTimeouts genericconf.HTTPServerTimeoutConfig, daReader DataAvailabilityServiceReader, daWriter DataAvailabilityServiceWriter, daHealthChecker DataAvailabilityServiceHealthChecker) (*http.Server, error) { +func StartDASRPCServer(ctx context.Context, addr string, portNum uint64, rpcServerTimeouts genericconf.HTTPServerTimeoutConfig, rpcServerBodyLimit int, daReader DataAvailabilityServiceReader, daWriter DataAvailabilityServiceWriter, daHealthChecker DataAvailabilityServiceHealthChecker, signatureVerifier *SignatureVerifier) (*http.Server, error) { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, portNum)) if err != nil { return nil, err } - return StartDASRPCServerOnListener(ctx, listener, rpcServerTimeouts, daReader, daWriter, daHealthChecker) + return StartDASRPCServerOnListener(ctx, listener, rpcServerTimeouts, rpcServerBodyLimit, daReader, daWriter, daHealthChecker, signatureVerifier) } -func StartDASRPCServerOnListener(ctx context.Context, listener net.Listener, rpcServerTimeouts genericconf.HTTPServerTimeoutConfig, daReader DataAvailabilityServiceReader, daWriter DataAvailabilityServiceWriter, daHealthChecker DataAvailabilityServiceHealthChecker) (*http.Server, error) { +func StartDASRPCServerOnListener(ctx context.Context, listener net.Listener, rpcServerTimeouts genericconf.HTTPServerTimeoutConfig, rpcServerBodyLimit int, daReader DataAvailabilityServiceReader, daWriter DataAvailabilityServiceWriter, daHealthChecker DataAvailabilityServiceHealthChecker, signatureVerifier *SignatureVerifier) (*http.Server, error) { if daWriter == nil { return nil, errors.New("No writer backend was configured for DAS RPC server. Has the BLS signing key been set up (--data-availability.key.key-dir or --data-availability.key.priv-key options)?") } rpcServer := rpc.NewServer() + if legacyDASStoreAPIOnly { + rpcServer.ApplyAPIFilter(map[string]bool{"das_store": true}) + } + if rpcServerBodyLimit > 0 { + rpcServer.SetHTTPBodyLimit(rpcServerBodyLimit) + } + err := rpcServer.RegisterName("das", &DASRPCServer{ - daReader: daReader, - daWriter: daWriter, - daHealthChecker: daHealthChecker, + daReader: daReader, + daWriter: daWriter, + daHealthChecker: daHealthChecker, + signatureVerifier: signatureVerifier, + batches: newBatchBuilder(), }) if err != nil { return nil, err @@ -88,8 +107,8 @@ type StoreResult struct { Version hexutil.Uint64 `json:"version,omitempty"` } -func (serv *DASRPCServer) Store(ctx context.Context, message hexutil.Bytes, timeout hexutil.Uint64, sig hexutil.Bytes) (*StoreResult, error) { - log.Trace("dasRpc.DASRPCServer.Store", "message", pretty.FirstFewBytes(message), "message length", len(message), "timeout", time.Unix(int64(timeout), 0), "sig", pretty.FirstFewBytes(sig), "this", serv) +func (s *DASRPCServer) Store(ctx context.Context, message hexutil.Bytes, timeout hexutil.Uint64, sig hexutil.Bytes) (*StoreResult, error) { + log.Trace("dasRpc.DASRPCServer.Store", "message", pretty.FirstFewBytes(message), "message length", len(message), "timeout", time.Unix(int64(timeout), 0), "sig", pretty.FirstFewBytes(sig), "this", s) rpcStoreRequestGauge.Inc(1) start := time.Now() success := false @@ -102,7 +121,220 @@ func (serv *DASRPCServer) Store(ctx context.Context, message hexutil.Bytes, time rpcStoreDurationHistogram.Update(time.Since(start).Nanoseconds()) }() - cert, err := serv.daWriter.Store(ctx, message, uint64(timeout), sig) + if err := s.signatureVerifier.verify(ctx, message, sig, uint64(timeout)); err != nil { + return nil, err + } + + cert, err := s.daWriter.Store(ctx, message, uint64(timeout)) + if err != nil { + return nil, err + } + rpcStoreStoredBytesGauge.Inc(int64(len(message))) + success = true + return &StoreResult{ + KeysetHash: cert.KeysetHash[:], + DataHash: cert.DataHash[:], + Timeout: hexutil.Uint64(cert.Timeout), + SignersMask: hexutil.Uint64(cert.SignersMask), + Sig: blsSignatures.SignatureToBytes(cert.Sig), + Version: hexutil.Uint64(cert.Version), + }, nil +} + +type StartChunkedStoreResult struct { + BatchId hexutil.Uint64 `json:"batchId,omitempty"` +} + +type SendChunkResult struct { + Ok hexutil.Uint64 `json:"sendChunkResult,omitempty"` +} + +type batch struct { + chunks [][]byte + expectedChunks uint64 + seenChunks atomic.Int64 + expectedChunkSize, expectedSize uint64 + timeout uint64 + startTime time.Time +} + +const ( + maxPendingBatches = 10 + batchBuildingExpiry = 1 * time.Minute +) + +// exposed global for test control +var ( + legacyDASStoreAPIOnly = false +) + +type batchBuilder struct { + mutex sync.Mutex + batches map[uint64]*batch +} + +func newBatchBuilder() *batchBuilder { + return &batchBuilder{ + batches: make(map[uint64]*batch), + } +} + +func (b *batchBuilder) assign(nChunks, timeout, chunkSize, totalSize uint64) (uint64, error) { + b.mutex.Lock() + defer b.mutex.Unlock() + if len(b.batches) >= maxPendingBatches { + return 0, fmt.Errorf("can't start new batch, already %d pending", len(b.batches)) + } + + id := rand.Uint64() + _, ok := b.batches[id] + if ok { + return 0, fmt.Errorf("can't start new batch, try again") + } + + b.batches[id] = &batch{ + chunks: make([][]byte, nChunks), + expectedChunks: nChunks, + expectedChunkSize: chunkSize, + expectedSize: totalSize, + timeout: timeout, + startTime: time.Now(), + } + go func(id uint64) { + <-time.After(batchBuildingExpiry) + b.mutex.Lock() + // Batch will only exist if expiry was reached without it being complete. + if _, exists := b.batches[id]; exists { + rpcStoreFailureGauge.Inc(1) + delete(b.batches, id) + } + b.mutex.Unlock() + }(id) + return id, nil +} + +func (b *batchBuilder) add(id, idx uint64, data []byte) error { + b.mutex.Lock() + batch, ok := b.batches[id] + b.mutex.Unlock() + if !ok { + return fmt.Errorf("unknown batch(%d)", id) + } + + if idx >= uint64(len(batch.chunks)) { + return fmt.Errorf("batch(%d): chunk(%d) out of range", id, idx) + } + + if batch.chunks[idx] != nil { + return fmt.Errorf("batch(%d): chunk(%d) already added", id, idx) + } + + if batch.expectedChunkSize < uint64(len(data)) { + return fmt.Errorf("batch(%d): chunk(%d) greater than expected size %d, was %d", id, idx, batch.expectedChunkSize, len(data)) + } + + batch.chunks[idx] = data + batch.seenChunks.Add(1) + return nil +} + +func (b *batchBuilder) close(id uint64) ([]byte, uint64, time.Time, error) { + b.mutex.Lock() + batch, ok := b.batches[id] + delete(b.batches, id) + b.mutex.Unlock() + if !ok { + return nil, 0, time.Time{}, fmt.Errorf("unknown batch(%d)", id) + } + + if batch.expectedChunks != uint64(batch.seenChunks.Load()) { + return nil, 0, time.Time{}, fmt.Errorf("incomplete batch(%d): got %d/%d chunks", id, batch.seenChunks.Load(), batch.expectedChunks) + } + + var flattened []byte + for _, chunk := range batch.chunks { + flattened = append(flattened, chunk...) + } + + if batch.expectedSize != uint64(len(flattened)) { + return nil, 0, time.Time{}, fmt.Errorf("batch(%d) was not expected size %d, was %d", id, batch.expectedSize, len(flattened)) + } + + return flattened, batch.timeout, batch.startTime, nil +} + +func (s *DASRPCServer) StartChunkedStore(ctx context.Context, timestamp, nChunks, chunkSize, totalSize, timeout hexutil.Uint64, sig hexutil.Bytes) (*StartChunkedStoreResult, error) { + rpcStoreRequestGauge.Inc(1) + failed := true + defer func() { + if failed { + rpcStoreFailureGauge.Inc(1) + } // success gague will be incremented on successful commit + }() + + if err := s.signatureVerifier.verify(ctx, []byte{}, sig, uint64(timestamp), uint64(nChunks), uint64(chunkSize), uint64(totalSize), uint64(timeout)); err != nil { + return nil, err + } + + // Prevent replay of old messages + if time.Since(time.Unix(int64(timestamp), 0)).Abs() > time.Minute { + return nil, errors.New("too much time has elapsed since request was signed") + } + + id, err := s.batches.assign(uint64(nChunks), uint64(timeout), uint64(chunkSize), uint64(totalSize)) + if err != nil { + return nil, err + } + + failed = false + return &StartChunkedStoreResult{ + BatchId: hexutil.Uint64(id), + }, nil + +} + +func (s *DASRPCServer) SendChunk(ctx context.Context, batchId, chunkId hexutil.Uint64, message hexutil.Bytes, sig hexutil.Bytes) error { + success := false + defer func() { + if success { + rpcSendChunkSuccessGauge.Inc(1) + } else { + rpcSendChunkFailureGauge.Inc(1) + } + }() + + if err := s.signatureVerifier.verify(ctx, message, sig, uint64(batchId), uint64(chunkId)); err != nil { + return err + } + + if err := s.batches.add(uint64(batchId), uint64(chunkId), message); err != nil { + return err + } + + success = true + return nil +} + +func (s *DASRPCServer) CommitChunkedStore(ctx context.Context, batchId hexutil.Uint64, sig hexutil.Bytes) (*StoreResult, error) { + if err := s.signatureVerifier.verify(ctx, []byte{}, sig, uint64(batchId)); err != nil { + return nil, err + } + + message, timeout, startTime, err := s.batches.close(uint64(batchId)) + if err != nil { + return nil, err + } + + cert, err := s.daWriter.Store(ctx, message, timeout) + success := false + defer func() { + if success { + rpcStoreSuccessGauge.Inc(1) + } else { + rpcStoreFailureGauge.Inc(1) + } + rpcStoreDurationHistogram.Update(time.Since(startTime).Nanoseconds()) + }() if err != nil { return nil, err } diff --git a/das/das_test.go b/das/das_test.go index 4377dc4dc..c52616fe2 100644 --- a/das/das_test.go +++ b/das/das_test.go @@ -47,9 +47,7 @@ func testDASStoreRetrieveMultipleInstances(t *testing.T, storageType string) { ParentChainNodeURL: "none", } - var syncFromStorageServicesFirst []*IterableStorageService - var syncToStorageServicesFirst []StorageService - storageService, lifecycleManager, err := CreatePersistentStorageService(firstCtx, &config, &syncFromStorageServicesFirst, &syncToStorageServicesFirst) + storageService, lifecycleManager, err := CreatePersistentStorageService(firstCtx, &config) Require(t, err) defer lifecycleManager.StopAndWaitUntil(time.Second) daWriter, err := NewSignAfterStoreDASWriter(firstCtx, config, storageService) @@ -58,7 +56,7 @@ func testDASStoreRetrieveMultipleInstances(t *testing.T, storageType string) { timeout := uint64(time.Now().Add(time.Hour * 24).Unix()) messageSaved := []byte("hello world") - cert, err := daWriter.Store(firstCtx, messageSaved, timeout, []byte{}) + cert, err := daWriter.Store(firstCtx, messageSaved, timeout) Require(t, err, "Error storing message") if cert.Timeout != timeout { Fail(t, fmt.Sprintf("Expected timeout of %d in cert, was %d", timeout, cert.Timeout)) @@ -77,9 +75,7 @@ func testDASStoreRetrieveMultipleInstances(t *testing.T, storageType string) { secondCtx, secondCancel := context.WithCancel(context.Background()) defer secondCancel() - var syncFromStorageServicesSecond []*IterableStorageService - var syncToStorageServicesSecond []StorageService - storageService2, lifecycleManager, err := CreatePersistentStorageService(secondCtx, &config, &syncFromStorageServicesSecond, &syncToStorageServicesSecond) + storageService2, lifecycleManager, err := CreatePersistentStorageService(secondCtx, &config) Require(t, err) defer lifecycleManager.StopAndWaitUntil(time.Second) var daReader2 DataAvailabilityServiceReader = storageService2 @@ -140,9 +136,7 @@ func testDASMissingMessage(t *testing.T, storageType string) { ParentChainNodeURL: "none", } - var syncFromStorageServices []*IterableStorageService - var syncToStorageServices []StorageService - storageService, lifecycleManager, err := CreatePersistentStorageService(ctx, &config, &syncFromStorageServices, &syncToStorageServices) + storageService, lifecycleManager, err := CreatePersistentStorageService(ctx, &config) Require(t, err) defer lifecycleManager.StopAndWaitUntil(time.Second) daWriter, err := NewSignAfterStoreDASWriter(ctx, config, storageService) @@ -151,7 +145,7 @@ func testDASMissingMessage(t *testing.T, storageType string) { messageSaved := []byte("hello world") timeout := uint64(time.Now().Add(time.Hour * 24).Unix()) - cert, err := daWriter.Store(ctx, messageSaved, timeout, []byte{}) + cert, err := daWriter.Store(ctx, messageSaved, timeout) Require(t, err, "Error storing message") if cert.Timeout != timeout { Fail(t, fmt.Sprintf("Expected timeout of %d in cert, was %d", timeout, cert.Timeout)) diff --git a/das/dastree/dastree.go b/das/dastree/dastree.go index bc325a320..d873f0568 100644 --- a/das/dastree/dastree.go +++ b/das/dastree/dastree.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -26,7 +27,7 @@ type node struct { // RecordHash chunks the preimage into 64kB bins and generates a recursive hash tree, // calling the caller-supplied record function for each hash/preimage pair created in // building the tree structure. -func RecordHash(record func(bytes32, []byte), preimage ...[]byte) bytes32 { +func RecordHash(record func(bytes32, []byte, arbutil.PreimageType), preimage ...[]byte) bytes32 { // Algorithm // 1. split the preimage into 64kB bins and double hash them to produce the tree's leaves // 2. repeatedly hash pairs and their combined length, bubbling up any odd-one's out, to form the root @@ -48,7 +49,7 @@ func RecordHash(record func(bytes32, []byte), preimage ...[]byte) bytes32 { keccord := func(value []byte) bytes32 { hash := crypto.Keccak256Hash(value) - record(hash, value) + record(hash, value, arbutil.Keccak256PreimageType) return hash } prepend := func(before byte, slice []byte) []byte { @@ -94,7 +95,7 @@ func RecordHash(record func(bytes32, []byte), preimage ...[]byte) bytes32 { func Hash(preimage ...[]byte) bytes32 { // Merkelizes without recording anything. All but the validator's DAS will call this - return RecordHash(func(bytes32, []byte) {}, preimage...) + return RecordHash(func(bytes32, []byte, arbutil.PreimageType) {}, preimage...) } func HashBytes(preimage ...[]byte) []byte { diff --git a/das/dastree/dastree_test.go b/das/dastree/dastree_test.go index 33f729f4f..4d24c9ae9 100644 --- a/das/dastree/dastree_test.go +++ b/das/dastree/dastree_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/colors" "github.com/offchainlabs/nitro/util/pretty" "github.com/offchainlabs/nitro/util/testhelpers" @@ -25,7 +26,7 @@ func TestDASTree(t *testing.T) { tests = append(tests, large) } - record := func(key bytes32, value []byte) { + record := func(key bytes32, value []byte, ty arbutil.PreimageType) { colors.PrintGrey("storing ", key, " ", pretty.PrettyBytes(value)) store[key] = value if crypto.Keccak256Hash(value) != key { diff --git a/das/db_storage_service.go b/das/db_storage_service.go index 33d21942b..0fbe1c272 100644 --- a/das/db_storage_service.go +++ b/das/db_storage_service.go @@ -12,7 +12,7 @@ import ( badger "github.com/dgraph-io/badger/v4" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -20,11 +20,9 @@ import ( ) type LocalDBStorageConfig struct { - Enable bool `koanf:"enable"` - DataDir string `koanf:"data-dir"` - DiscardAfterTimeout bool `koanf:"discard-after-timeout"` - SyncFromStorageService bool `koanf:"sync-from-storage-service"` - SyncToStorageService bool `koanf:"sync-to-storage-service"` + Enable bool `koanf:"enable"` + DataDir string `koanf:"data-dir"` + DiscardAfterTimeout bool `koanf:"discard-after-timeout"` // BadgerDB options NumMemtables int `koanf:"num-memtables"` @@ -38,11 +36,9 @@ type LocalDBStorageConfig struct { var badgerDefaultOptions = badger.DefaultOptions("") var DefaultLocalDBStorageConfig = LocalDBStorageConfig{ - Enable: false, - DataDir: "", - DiscardAfterTimeout: false, - SyncFromStorageService: false, - SyncToStorageService: false, + Enable: false, + DataDir: "", + DiscardAfterTimeout: false, NumMemtables: badgerDefaultOptions.NumMemtables, NumLevelZeroTables: badgerDefaultOptions.NumLevelZeroTables, @@ -56,8 +52,6 @@ func LocalDBStorageConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultLocalDBStorageConfig.Enable, "enable storage/retrieval of sequencer batch data from a database on the local filesystem") f.String(prefix+".data-dir", DefaultLocalDBStorageConfig.DataDir, "directory in which to store the database") f.Bool(prefix+".discard-after-timeout", DefaultLocalDBStorageConfig.DiscardAfterTimeout, "discard data after its expiry timeout") - f.Bool(prefix+".sync-from-storage-service", DefaultLocalDBStorageConfig.SyncFromStorageService, "enable db storage to be used as a source for regular sync storage") - f.Bool(prefix+".sync-to-storage-service", DefaultLocalDBStorageConfig.SyncToStorageService, "enable db storage to be used as a sink for regular sync storage") f.Int(prefix+".num-memtables", DefaultLocalDBStorageConfig.NumMemtables, "BadgerDB option: sets the maximum number of tables to keep in memory before stalling") f.Int(prefix+".num-level-zero-tables", DefaultLocalDBStorageConfig.NumLevelZeroTables, "BadgerDB option: sets the maximum number of Level 0 tables before compaction starts") @@ -158,13 +152,6 @@ func (dbs *DBStorageService) Put(ctx context.Context, data []byte, timeout uint6 }) } -func (dbs *DBStorageService) putKeyValue(ctx context.Context, key common.Hash, value []byte) error { - return dbs.db.Update(func(txn *badger.Txn) error { - e := badger.NewEntry(key.Bytes(), value) - return txn.SetEntry(e) - }) -} - func (dbs *DBStorageService) Sync(ctx context.Context) error { return dbs.db.Sync() } @@ -173,11 +160,11 @@ func (dbs *DBStorageService) Close(ctx context.Context) error { return dbs.stopWaiter.StopAndWait() } -func (dbs *DBStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (dbs *DBStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { if dbs.discardAfterTimeout { - return arbstate.DiscardAfterDataTimeout, nil + return daprovider.DiscardAfterDataTimeout, nil } - return arbstate.KeepForever, nil + return daprovider.KeepForever, nil } func (dbs *DBStorageService) String() string { diff --git a/das/extra_signature_checker_test.go b/das/extra_signature_checker_test.go index 88a096922..11c218ae0 100644 --- a/das/extra_signature_checker_test.go +++ b/das/extra_signature_checker_test.go @@ -5,25 +5,19 @@ package das import ( "bytes" - "context" "encoding/hex" "errors" "io/ioutil" "testing" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/util/signature" ) -type StubSignatureCheckDAS struct { - keyDir string -} - -func (s *StubSignatureCheckDAS) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) { - pubkeyEncoded, err := ioutil.ReadFile(s.keyDir + "/ecdsa.pub") +func checkSig(keyDir string, message []byte, timeout uint64, sig []byte) (*daprovider.DataAvailabilityCertificate, error) { + pubkeyEncoded, err := ioutil.ReadFile(keyDir + "/ecdsa.pub") if err != nil { return nil, err } @@ -39,22 +33,6 @@ func (s *StubSignatureCheckDAS) Store(ctx context.Context, message []byte, timeo return nil, nil } -func (s *StubSignatureCheckDAS) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { - return arbstate.KeepForever, nil -} - -func (s *StubSignatureCheckDAS) GetByHash(ctx context.Context, hash common.Hash) ([]byte, error) { - return []byte{}, nil -} - -func (s *StubSignatureCheckDAS) HealthCheck(ctx context.Context) error { - return nil -} - -func (s *StubSignatureCheckDAS) String() string { - return "StubSignatureCheckDAS" -} - func TestExtraSignatureCheck(t *testing.T) { keyDir := t.TempDir() err := GenerateAndStoreECDSAKeys(keyDir) @@ -64,11 +42,11 @@ func TestExtraSignatureCheck(t *testing.T) { Require(t, err) signer := signature.DataSignerFromPrivateKey(privateKey) - var da DataAvailabilityServiceWriter = &StubSignatureCheckDAS{keyDir} - da, err = NewStoreSigningDAS(da, signer) + msg := []byte("Hello world") + timeout := uint64(1234) + sig, err := applyDasSigner(signer, msg, timeout) Require(t, err) - - _, err = da.Store(context.Background(), []byte("Hello world"), 1234, []byte{}) + _, err = checkSig(keyDir, msg, timeout, sig) Require(t, err) } diff --git a/das/factory.go b/das/factory.go index a459d1a46..d9eacd0ad 100644 --- a/das/factory.go +++ b/das/factory.go @@ -22,8 +22,6 @@ import ( func CreatePersistentStorageService( ctx context.Context, config *DataAvailabilityConfig, - syncFromStorageServices *[]*IterableStorageService, - syncToStorageServices *[]StorageService, ) (StorageService, *LifecycleManager, error) { storageServices := make([]StorageService, 0, 10) var lifecycleManager LifecycleManager @@ -32,14 +30,6 @@ func CreatePersistentStorageService( if err != nil { return nil, nil, err } - if config.LocalDBStorage.SyncFromStorageService { - iterableStorageService := NewIterableStorageService(ConvertStorageServiceToIterationCompatibleStorageService(s)) - *syncFromStorageServices = append(*syncFromStorageServices, iterableStorageService) - s = iterableStorageService - } - if config.LocalDBStorage.SyncToStorageService { - *syncToStorageServices = append(*syncToStorageServices, s) - } lifecycleManager.Register(s) storageServices = append(storageServices, s) } @@ -49,14 +39,6 @@ func CreatePersistentStorageService( if err != nil { return nil, nil, err } - if config.LocalFileStorage.SyncFromStorageService { - iterableStorageService := NewIterableStorageService(ConvertStorageServiceToIterationCompatibleStorageService(s)) - *syncFromStorageServices = append(*syncFromStorageServices, iterableStorageService) - s = iterableStorageService - } - if config.LocalFileStorage.SyncToStorageService { - *syncToStorageServices = append(*syncToStorageServices, s) - } lifecycleManager.Register(s) storageServices = append(storageServices, s) } @@ -67,23 +49,6 @@ func CreatePersistentStorageService( return nil, nil, err } lifecycleManager.Register(s) - if config.S3Storage.SyncFromStorageService { - iterableStorageService := NewIterableStorageService(ConvertStorageServiceToIterationCompatibleStorageService(s)) - *syncFromStorageServices = append(*syncFromStorageServices, iterableStorageService) - s = iterableStorageService - } - if config.S3Storage.SyncToStorageService { - *syncToStorageServices = append(*syncToStorageServices, s) - } - storageServices = append(storageServices, s) - } - - if config.IpfsStorage.Enable { - s, err := NewIpfsStorageService(ctx, config.IpfsStorage) - if err != nil { - return nil, nil, err - } - lifecycleManager.Register(s) storageServices = append(storageServices, s) } @@ -105,8 +70,6 @@ func WrapStorageWithCache( ctx context.Context, config *DataAvailabilityConfig, storageService StorageService, - syncFromStorageServices *[]*IterableStorageService, - syncToStorageServices *[]StorageService, lifecycleManager *LifecycleManager) (StorageService, error) { if storageService == nil { return nil, nil @@ -120,14 +83,6 @@ func WrapStorageWithCache( if err != nil { return nil, err } - if config.RedisCache.SyncFromStorageService { - iterableStorageService := NewIterableStorageService(ConvertStorageServiceToIterationCompatibleStorageService(storageService)) - *syncFromStorageServices = append(*syncFromStorageServices, iterableStorageService) - storageService = iterableStorageService - } - if config.RedisCache.SyncToStorageService { - *syncToStorageServices = append(*syncToStorageServices, storageService) - } } if config.LocalCache.Enable { storageService = NewCacheStorageService(config.LocalCache, storageService) @@ -151,24 +106,13 @@ func CreateBatchPosterDAS( if !config.RPCAggregator.Enable || !config.RestAggregator.Enable { return nil, nil, nil, errors.New("--node.data-availability.rpc-aggregator.enable and rest-aggregator.enable must be set when running a Batch Poster in AnyTrust mode") } - - if config.IpfsStorage.Enable { - return nil, nil, nil, errors.New("--node.data-availability.ipfs-storage.enable may not be set when running a Nitro AnyTrust node in Batch Poster mode") - } // Done checking config requirements var daWriter DataAvailabilityServiceWriter - daWriter, err := NewRPCAggregator(ctx, *config) + daWriter, err := NewRPCAggregator(ctx, *config, dataSigner) if err != nil { return nil, nil, nil, err } - if dataSigner != nil { - // In some tests the batch poster does not sign Store requests - daWriter, err = NewStoreSigningDAS(daWriter, dataSigner) - if err != nil { - return nil, nil, nil, err - } - } restAgg, err := NewRestfulClientAggregator(ctx, &config.RestAggregator) if err != nil { @@ -191,30 +135,27 @@ func CreateDAComponentsForDaserver( config *DataAvailabilityConfig, l1Reader *headerreader.HeaderReader, seqInboxAddress *common.Address, -) (DataAvailabilityServiceReader, DataAvailabilityServiceWriter, DataAvailabilityServiceHealthChecker, *LifecycleManager, error) { +) (DataAvailabilityServiceReader, DataAvailabilityServiceWriter, *SignatureVerifier, DataAvailabilityServiceHealthChecker, *LifecycleManager, error) { if !config.Enable { - return nil, nil, nil, nil, nil + return nil, nil, nil, nil, nil, nil } // Check config requirements if !config.LocalDBStorage.Enable && !config.LocalFileStorage.Enable && - !config.S3Storage.Enable && - !config.IpfsStorage.Enable { - return nil, nil, nil, nil, errors.New("At least one of --data-availability.(local-db-storage|local-file-storage|s3-storage|ipfs-storage) must be enabled.") + !config.S3Storage.Enable { + return nil, nil, nil, nil, nil, errors.New("At least one of --data-availability.(local-db-storage|local-file-storage|s3-storage) must be enabled.") } // Done checking config requirements - var syncFromStorageServices []*IterableStorageService - var syncToStorageServices []StorageService - storageService, dasLifecycleManager, err := CreatePersistentStorageService(ctx, config, &syncFromStorageServices, &syncToStorageServices) + storageService, dasLifecycleManager, err := CreatePersistentStorageService(ctx, config) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } - storageService, err = WrapStorageWithCache(ctx, config, storageService, &syncFromStorageServices, &syncToStorageServices, dasLifecycleManager) + storageService, err = WrapStorageWithCache(ctx, config, storageService, dasLifecycleManager) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } // The REST aggregator is used as the fallback if requested data is not present @@ -222,7 +163,7 @@ func CreateDAComponentsForDaserver( if config.RestAggregator.Enable { restAgg, err := NewRestfulClientAggregator(ctx, &config.RestAggregator) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } restAgg.Start(ctx) dasLifecycleManager.Register(restAgg) @@ -237,7 +178,7 @@ func CreateDAComponentsForDaserver( if syncConf.Eager { if l1Reader == nil || seqInboxAddress == nil { - return nil, nil, nil, nil, errors.New("l1-node-url and sequencer-inbox-address must be specified along with sync-to-storage.eager") + return nil, nil, nil, nil, nil, errors.New("l1-node-url and sequencer-inbox-address must be specified along with sync-to-storage.eager") } storageService, err = NewSyncingFallbackStorageService( ctx, @@ -249,7 +190,7 @@ func CreateDAComponentsForDaserver( syncConf) dasLifecycleManager.Register(storageService) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } } else { storageService = NewFallbackStorageService(storageService, restAgg, restAgg, @@ -262,13 +203,14 @@ func CreateDAComponentsForDaserver( var daWriter DataAvailabilityServiceWriter var daReader DataAvailabilityServiceReader = storageService var daHealthChecker DataAvailabilityServiceHealthChecker = storageService + var signatureVerifier *SignatureVerifier if config.Key.KeyDir != "" || config.Key.PrivKey != "" { var seqInboxCaller *bridgegen.SequencerInboxCaller if seqInboxAddress != nil { seqInbox, err := bridgegen.NewSequencerInbox(*seqInboxAddress, (*l1Reader).Client()) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } seqInboxCaller = &seqInbox.SequencerInboxCaller @@ -277,35 +219,28 @@ func CreateDAComponentsForDaserver( seqInboxCaller = nil } - privKey, err := config.Key.BLSPrivKey() + daWriter, err = NewSignAfterStoreDASWriter(ctx, *config, storageService) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } - daWriter, err = NewSignAfterStoreDASWriterWithSeqInboxCaller( - privKey, + signatureVerifier, err = NewSignatureVerifierWithSeqInboxCaller( seqInboxCaller, - storageService, config.ExtraSignatureCheckingPublicKey, ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } } - if config.RegularSyncStorage.Enable && len(syncFromStorageServices) != 0 && len(syncToStorageServices) != 0 { - regularlySyncStorage := NewRegularlySyncStorage(syncFromStorageServices, syncToStorageServices, config.RegularSyncStorage) - regularlySyncStorage.Start(ctx) - } - if seqInboxAddress != nil { daReader, err = NewChainFetchReader(daReader, (*l1Reader).Client(), *seqInboxAddress) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } } - return daReader, daWriter, daHealthChecker, dasLifecycleManager, nil + return daReader, daWriter, signatureVerifier, daHealthChecker, dasLifecycleManager, nil } func CreateDAReaderForNode( @@ -323,48 +258,22 @@ func CreateDAReaderForNode( return nil, nil, errors.New("node.data-availability.rpc-aggregator is only for Batch Poster mode") } - if !config.RestAggregator.Enable && !config.IpfsStorage.Enable { - return nil, nil, fmt.Errorf("--node.data-availability.enable was set but neither of --node.data-availability.(rest-aggregator|ipfs-storage) were enabled. When running a Nitro Anytrust node in non-Batch Poster mode, some way to get the batch data is required.") - } - - if config.RestAggregator.SyncToStorage.Eager { - return nil, nil, errors.New("--node.data-availability.rest-aggregator.sync-to-storage.eager can't be used with a Nitro node, only lazy syncing can be used.") + if !config.RestAggregator.Enable { + return nil, nil, fmt.Errorf("--node.data-availability.enable was set but not --node.data-availability.rest-aggregator. When running a Nitro Anytrust node in non-Batch Poster mode, some way to get the batch data is required.") } // Done checking config requirements - storageService, dasLifecycleManager, err := CreatePersistentStorageService(ctx, config, nil, nil) - if err != nil { - return nil, nil, err - } - + var lifecycleManager LifecycleManager var daReader DataAvailabilityServiceReader if config.RestAggregator.Enable { var restAgg *SimpleDASReaderAggregator - restAgg, err = NewRestfulClientAggregator(ctx, &config.RestAggregator) + restAgg, err := NewRestfulClientAggregator(ctx, &config.RestAggregator) if err != nil { return nil, nil, err } restAgg.Start(ctx) - dasLifecycleManager.Register(restAgg) - - if storageService != nil { - syncConf := &config.RestAggregator.SyncToStorage - var retentionPeriodSeconds uint64 - if uint64(syncConf.RetentionPeriod) == math.MaxUint64 { - retentionPeriodSeconds = math.MaxUint64 - } else { - retentionPeriodSeconds = uint64(syncConf.RetentionPeriod.Seconds()) - } - - // This falls back to REST and updates the local IPFS repo if the data is found. - storageService = NewFallbackStorageService(storageService, restAgg, restAgg, - retentionPeriodSeconds, syncConf.IgnoreWriteErrors, true) - dasLifecycleManager.Register(storageService) - - daReader = storageService - } else { - daReader = restAgg - } + lifecycleManager.Register(restAgg) + daReader = restAgg } if seqInboxAddress != nil { @@ -378,5 +287,5 @@ func CreateDAReaderForNode( } } - return daReader, dasLifecycleManager, nil + return daReader, &lifecycleManager, nil } diff --git a/das/fallback_storage_service.go b/das/fallback_storage_service.go index a78b4104e..49f961da6 100644 --- a/das/fallback_storage_service.go +++ b/das/fallback_storage_service.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/pretty" @@ -18,7 +18,7 @@ import ( type FallbackStorageService struct { StorageService - backup arbstate.DataAvailabilityReader + backup daprovider.DASReader backupHealthChecker DataAvailabilityServiceHealthChecker backupRetentionSeconds uint64 ignoreRetentionWriteErrors bool @@ -32,7 +32,7 @@ type FallbackStorageService struct { // a successful GetByHash result from the backup is Put into the primary. func NewFallbackStorageService( primary StorageService, - backup arbstate.DataAvailabilityReader, + backup daprovider.DASReader, backupHealthChecker DataAvailabilityServiceHealthChecker, backupRetentionSeconds uint64, // how long to retain data that we copy in from the backup (MaxUint64 means forever) ignoreRetentionWriteErrors bool, // if true, don't return error if write of retention data to primary fails diff --git a/das/ipfs_storage_service.go b/das/ipfs_storage_service.go deleted file mode 100644 index 4f73242c2..000000000 --- a/das/ipfs_storage_service.go +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -// IPFS DAS backend. -// It takes advantage of IPFS' content addressing scheme to be able to directly retrieve -// the batches from IPFS using their root hash from the L1 sequencer inbox contract. - -package das - -import ( - "bytes" - "context" - "errors" - "io" - "math/rand" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ipfs/go-cid" - coreiface "github.com/ipfs/interface-go-ipfs-core" - "github.com/ipfs/interface-go-ipfs-core/options" - "github.com/ipfs/interface-go-ipfs-core/path" - "github.com/multiformats/go-multihash" - "github.com/offchainlabs/nitro/arbstate" - "github.com/offchainlabs/nitro/cmd/ipfshelper" - "github.com/offchainlabs/nitro/das/dastree" - "github.com/offchainlabs/nitro/util/pretty" - flag "github.com/spf13/pflag" -) - -type IpfsStorageServiceConfig struct { - Enable bool `koanf:"enable"` - RepoDir string `koanf:"repo-dir"` - ReadTimeout time.Duration `koanf:"read-timeout"` - Profiles string `koanf:"profiles"` - Peers []string `koanf:"peers"` - - // Pinning options - PinAfterGet bool `koanf:"pin-after-get"` - PinPercentage float64 `koanf:"pin-percentage"` -} - -var DefaultIpfsStorageServiceConfig = IpfsStorageServiceConfig{ - Enable: false, - RepoDir: "", - ReadTimeout: time.Minute, - Profiles: "", - Peers: []string{}, - - PinAfterGet: true, - PinPercentage: 100.0, -} - -func IpfsStorageServiceConfigAddOptions(prefix string, f *flag.FlagSet) { - f.Bool(prefix+".enable", DefaultIpfsStorageServiceConfig.Enable, "enable storage/retrieval of sequencer batch data from IPFS") - f.String(prefix+".repo-dir", DefaultIpfsStorageServiceConfig.RepoDir, "directory to use to store the local IPFS repo") - f.Duration(prefix+".read-timeout", DefaultIpfsStorageServiceConfig.ReadTimeout, "timeout for IPFS reads, since by default it will wait forever. Treat timeout as not found") - f.String(prefix+".profiles", DefaultIpfsStorageServiceConfig.Profiles, "comma separated list of IPFS profiles to use, see https://docs.ipfs.tech/how-to/default-profile") - f.StringSlice(prefix+".peers", DefaultIpfsStorageServiceConfig.Peers, "list of IPFS peers to connect to, eg /ip4/1.2.3.4/tcp/12345/p2p/abc...xyz") - f.Bool(prefix+".pin-after-get", DefaultIpfsStorageServiceConfig.PinAfterGet, "pin sequencer batch data in IPFS") - f.Float64(prefix+".pin-percentage", DefaultIpfsStorageServiceConfig.PinPercentage, "percent of sequencer batch data to pin, as a floating point number in the range 0.0 to 100.0") -} - -type IpfsStorageService struct { - config IpfsStorageServiceConfig - ipfsHelper *ipfshelper.IpfsHelper - ipfsApi coreiface.CoreAPI -} - -func NewIpfsStorageService(ctx context.Context, config IpfsStorageServiceConfig) (*IpfsStorageService, error) { - ipfsHelper, err := ipfshelper.CreateIpfsHelper(ctx, config.RepoDir, false, config.Peers, config.Profiles) - if err != nil { - return nil, err - } - addrs, err := ipfsHelper.GetPeerHostAddresses() - if err != nil { - return nil, err - } - log.Info("IPFS node started up", "hostAddresses", addrs) - - return &IpfsStorageService{ - config: config, - ipfsHelper: ipfsHelper, - ipfsApi: ipfsHelper.GetAPI(), - }, nil -} - -func hashToCid(hash common.Hash) (cid.Cid, error) { - multiEncodedHashBytes, err := multihash.Encode(hash[:], multihash.KECCAK_256) - if err != nil { - return cid.Cid{}, err - } - - _, multiHash, err := multihash.MHFromBytes(multiEncodedHashBytes) - if err != nil { - return cid.Cid{}, err - } - - return cid.NewCidV1(cid.Raw, multiHash), nil -} - -// GetByHash retrieves and reconstructs one batch's data, using IPFS to retrieve the preimages -// for each chunk of data and the dastree nodes. -func (s *IpfsStorageService) GetByHash(ctx context.Context, hash common.Hash) ([]byte, error) { - log.Trace("das.IpfsStorageService.GetByHash", "hash", pretty.PrettyHash(hash)) - - doPin := false // If true, pin every block related to this batch - if s.config.PinAfterGet { - if s.config.PinPercentage == 100.0 { - doPin = true - } else if (rand.Float64() * 100.0) <= s.config.PinPercentage { - doPin = true - } - - } - - oracle := func(h common.Hash) ([]byte, error) { - thisCid, err := hashToCid(h) - if err != nil { - return nil, err - } - - ipfsPath := path.IpfsPath(thisCid) - log.Trace("Retrieving IPFS path", "path", ipfsPath.String()) - - parentCtx := ctx - if doPin { - // If we want to pin this batch, then detach from the parent context so - // we are not canceled before s.config.ReadTimeout. - parentCtx = context.Background() - } - - timeoutCtx, cancel := context.WithTimeout(parentCtx, s.config.ReadTimeout) - defer cancel() - rdr, err := s.ipfsApi.Block().Get(timeoutCtx, ipfsPath) - if err != nil { - if timeoutCtx.Err() != nil { - return nil, ErrNotFound - } - return nil, err - } - - data, err := io.ReadAll(rdr) - if err != nil { - return nil, err - } - - if doPin { - go func() { - pinCtx, pinCancel := context.WithTimeout(context.Background(), s.config.ReadTimeout) - defer pinCancel() - err := s.ipfsApi.Pin().Add(pinCtx, ipfsPath) - // Recursive pinning not needed, each dastree preimage fits in a single - // IPFS block. - if err != nil { - // Pinning is best-effort. - log.Warn("Failed to pin in IPFS", "hash", pretty.PrettyHash(hash), "path", ipfsPath.String()) - } else { - log.Trace("Pin in IPFS successful", "hash", pretty.PrettyHash(hash), "path", ipfsPath.String()) - } - }() - } - - return data, nil - } - - return dastree.Content(hash, oracle) -} - -// Put stores all the preimages required to reconstruct the dastree for single batch, -// ie the hashed data chunks and dastree nodes. -// This takes advantage of IPFS supporting keccak256 on raw data blocks for calculating -// its CIDs, and the fact that the dastree structure uses keccak256 for addressing its -// nodes, to directly store the dastree structure in IPFS. -// IPFS default block size is 256KB and dastree max block size is 64KB so each dastree -// node and data chunk easily fits within an IPFS block. -func (s *IpfsStorageService) Put(ctx context.Context, data []byte, timeout uint64) error { - logPut("das.IpfsStorageService.Put", data, timeout, s) - - var chunks [][]byte - - record := func(_ common.Hash, value []byte) { - chunks = append(chunks, value) - } - - _ = dastree.RecordHash(record, data) - - numChunks := len(chunks) - resultChan := make(chan error, numChunks) - for _, chunk := range chunks { - _chunk := chunk - go func() { - blockStat, err := s.ipfsApi.Block().Put( - ctx, - bytes.NewReader(_chunk), - options.Block.CidCodec("raw"), // Store the data in raw form since the hash in the CID must be the hash - // of the preimage for our lookup scheme to work. - options.Block.Hash(multihash.KECCAK_256, -1), // Use keccak256 to calculate the hash to put in the block's - // CID, since it is the same algo used by dastree. - options.Block.Pin(true)) // Keep the data in the local IPFS repo, don't GC it. - if err == nil { - log.Trace("Wrote IPFS path", "path", blockStat.Path().String()) - } - resultChan <- err - }() - } - - successfullyWrittenChunks := 0 - for err := range resultChan { - if err != nil { - return err - } - successfullyWrittenChunks++ - if successfullyWrittenChunks == numChunks { - return nil - } - } - panic("unreachable") -} - -func (s *IpfsStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { - return arbstate.KeepForever, nil -} - -func (s *IpfsStorageService) Sync(ctx context.Context) error { - return nil -} - -func (s *IpfsStorageService) Close(ctx context.Context) error { - return s.ipfsHelper.Close() -} - -func (s *IpfsStorageService) String() string { - return "IpfsStorageService" -} - -func (s *IpfsStorageService) HealthCheck(ctx context.Context) error { - testData := []byte("Test-Data") - err := s.Put(ctx, testData, 0) - if err != nil { - return err - } - res, err := s.GetByHash(ctx, dastree.Hash(testData)) - if err != nil { - return err - } - if !bytes.Equal(res, testData) { - return errors.New("invalid GetByHash result") - } - return nil -} diff --git a/das/ipfs_storage_service_test.go b/das/ipfs_storage_service_test.go deleted file mode 100644 index 54d6705c8..000000000 --- a/das/ipfs_storage_service_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -package das - -import ( - "bytes" - "context" - "math" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/offchainlabs/nitro/das/dastree" -) - -func runAddAndGetTest(t *testing.T, ctx context.Context, svc *IpfsStorageService, size int) { - - data := make([]byte, size) - _, err := rand.Read(data) - Require(t, err) - - err = svc.Put(ctx, data, 0) - Require(t, err) - - hash := dastree.Hash(data).Bytes() - returnedData, err := svc.GetByHash(ctx, common.BytesToHash(hash)) - Require(t, err) - if !bytes.Equal(data, returnedData) { - Fail(t, "Returned data didn't match!") - } - -} - -func TestIpfsStorageServiceAddAndGet(t *testing.T) { - enableLogging() - ctx := context.Background() - svc, err := NewIpfsStorageService(ctx, - IpfsStorageServiceConfig{ - Enable: true, - RepoDir: t.TempDir(), - ReadTimeout: time.Minute, - Profiles: "test", - }) - defer svc.Close(ctx) - Require(t, err) - - pow2Size := 1 << 16 // 64kB - for i := 1; i < 8; i++ { - runAddAndGetTest(t, ctx, svc, int(math.Pow10(i))) - runAddAndGetTest(t, ctx, svc, pow2Size) - runAddAndGetTest(t, ctx, svc, pow2Size-1) - runAddAndGetTest(t, ctx, svc, pow2Size+1) - pow2Size = pow2Size << 1 - } -} diff --git a/das/iterable_storage_service.go b/das/iterable_storage_service.go deleted file mode 100644 index a0829f00e..000000000 --- a/das/iterable_storage_service.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -package das - -import ( - "context" - "strconv" - "sync" - "sync/atomic" - - "github.com/ethereum/go-ethereum/common" - - "github.com/offchainlabs/nitro/das/dastree" -) - -const iteratorStorageKeyPrefix = "iterator_key_prefix_" -const iteratorBegin = "iterator_begin" -const iteratorEnd = "iterator_end" -const expirationTimeKeyPrefix = "expiration_time_key_prefix_" - -// IterationCompatibleStorageService is a StorageService which is -// compatible to be used as a backend for IterableStorageService. -type IterationCompatibleStorageService interface { - putKeyValue(ctx context.Context, key common.Hash, value []byte) error - StorageService -} - -// IterationCompatibleStorageServiceAdaptor is an adaptor used to covert iteration incompatible StorageService -// to IterationCompatibleStorageService (basically adds an empty putKeyValue to the StorageService) -type IterationCompatibleStorageServiceAdaptor struct { - StorageService -} - -func (i *IterationCompatibleStorageServiceAdaptor) putKeyValue(ctx context.Context, key common.Hash, value []byte) error { - return nil -} - -func ConvertStorageServiceToIterationCompatibleStorageService(storageService StorageService) IterationCompatibleStorageService { - service, ok := storageService.(IterationCompatibleStorageService) - if ok { - return service - } - return &IterationCompatibleStorageServiceAdaptor{storageService} -} - -// An IterableStorageService is used as a wrapper on top of a storage service, -// to add the capability of iterating over the stored date in a sequential manner. -type IterableStorageService struct { - // Local copy of iterator end. End can also be accessed by getByHash for iteratorEnd. - end atomic.Value // atomic access to common.Hash - IterationCompatibleStorageService - - mutex sync.Mutex -} - -func NewIterableStorageService(storageService IterationCompatibleStorageService) *IterableStorageService { - i := &IterableStorageService{IterationCompatibleStorageService: storageService} - i.end.Store(common.Hash{}) - return i -} - -func (i *IterableStorageService) Put(ctx context.Context, data []byte, expiration uint64) error { - dataHash := dastree.Hash(data) - - // Do not insert data if data is already present. - // (This is being done to avoid redundant hash being added to the - // linked list ,since it can lead to loops in the linked list.) - if _, err := i.IterationCompatibleStorageService.GetByHash(ctx, dataHash); err == nil { - return nil - } - - if err := i.IterationCompatibleStorageService.Put(ctx, data, expiration); err != nil { - return err - } - - if err := i.putKeyValue(ctx, dastree.Hash([]byte(expirationTimeKeyPrefix+EncodeStorageServiceKey(dastree.Hash(data)))), []byte(strconv.FormatUint(expiration, 10))); err != nil { - return err - } - - i.mutex.Lock() - defer i.mutex.Unlock() - - endHash := i.End(ctx) - if (endHash == common.Hash{}) { - // First element being inserted in the chain. - if err := i.putKeyValue(ctx, dastree.Hash([]byte(iteratorBegin)), dataHash.Bytes()); err != nil { - return err - } - } else { - if err := i.putKeyValue(ctx, dastree.Hash([]byte(iteratorStorageKeyPrefix+EncodeStorageServiceKey(endHash))), dataHash.Bytes()); err != nil { - return err - } - } - - if err := i.putKeyValue(ctx, dastree.Hash([]byte(iteratorEnd)), dataHash.Bytes()); err != nil { - return err - } - i.end.Store(dataHash) - - return nil -} - -func (i *IterableStorageService) GetExpirationTime(ctx context.Context, hash common.Hash) (uint64, error) { - value, err := i.IterationCompatibleStorageService.GetByHash(ctx, dastree.Hash([]byte(expirationTimeKeyPrefix+EncodeStorageServiceKey(hash)))) - if err != nil { - return 0, err - } - - expirationTime, err := strconv.ParseUint(string(value), 10, 64) - if err != nil { - return 0, err - } - return expirationTime, nil -} - -func (i *IterableStorageService) DefaultBegin() common.Hash { - return dastree.Hash([]byte(iteratorBegin)) -} - -func (i *IterableStorageService) End(ctx context.Context) common.Hash { - endHash, ok := i.end.Load().(common.Hash) - if !ok { - return common.Hash{} - } - if (endHash != common.Hash{}) { - return endHash - } - value, err := i.GetByHash(ctx, dastree.Hash([]byte(iteratorEnd))) - if err != nil { - return common.Hash{} - } - endHash = common.BytesToHash(value) - i.end.Store(endHash) - return endHash -} - -func (i *IterableStorageService) Next(ctx context.Context, hash common.Hash) common.Hash { - if hash != i.DefaultBegin() { - hash = dastree.Hash([]byte(iteratorStorageKeyPrefix + EncodeStorageServiceKey(hash))) - } - value, err := i.GetByHash(ctx, hash) - if err != nil { - return common.Hash{} - } - return common.BytesToHash(value) -} diff --git a/das/local_file_storage_service.go b/das/local_file_storage_service.go index 5fa5306e3..8be03bcb3 100644 --- a/das/local_file_storage_service.go +++ b/das/local_file_storage_service.go @@ -14,7 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" flag "github.com/spf13/pflag" @@ -22,10 +22,8 @@ import ( ) type LocalFileStorageConfig struct { - Enable bool `koanf:"enable"` - DataDir string `koanf:"data-dir"` - SyncFromStorageService bool `koanf:"sync-from-storage-service"` - SyncToStorageService bool `koanf:"sync-to-storage-service"` + Enable bool `koanf:"enable"` + DataDir string `koanf:"data-dir"` } var DefaultLocalFileStorageConfig = LocalFileStorageConfig{ @@ -35,8 +33,6 @@ var DefaultLocalFileStorageConfig = LocalFileStorageConfig{ func LocalFileStorageConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultLocalFileStorageConfig.Enable, "enable storage/retrieval of sequencer batch data from a directory of files, one per batch") f.String(prefix+".data-dir", DefaultLocalFileStorageConfig.DataDir, "local data directory") - f.Bool(prefix+".sync-from-storage-service", DefaultLocalFileStorageConfig.SyncFromStorageService, "enable local storage to be used as a source for regular sync storage") - f.Bool(prefix+".sync-to-storage-service", DefaultLocalFileStorageConfig.SyncToStorageService, "enable local storage to be used as a sink for regular sync storage") } type LocalFileStorageService struct { @@ -96,32 +92,6 @@ func (s *LocalFileStorageService) Put(ctx context.Context, data []byte, timeout } -func (s *LocalFileStorageService) putKeyValue(ctx context.Context, key common.Hash, value []byte) error { - fileName := EncodeStorageServiceKey(key) - finalPath := s.dataDir + "/" + fileName - - // Use a temp file and rename to achieve atomic writes. - f, err := os.CreateTemp(s.dataDir, fileName) - if err != nil { - return err - } - err = f.Chmod(0o600) - if err != nil { - return err - } - _, err = f.Write(value) - if err != nil { - return err - } - err = f.Close() - if err != nil { - return err - } - - return os.Rename(f.Name(), finalPath) - -} - func (s *LocalFileStorageService) Sync(ctx context.Context) error { return nil } @@ -130,8 +100,8 @@ func (s *LocalFileStorageService) Close(ctx context.Context) error { return nil } -func (s *LocalFileStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { - return arbstate.KeepForever, nil +func (s *LocalFileStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { + return daprovider.KeepForever, nil } func (s *LocalFileStorageService) String() string { diff --git a/das/memory_backed_storage_service.go b/das/memory_backed_storage_service.go index 648423147..c013b501b 100644 --- a/das/memory_backed_storage_service.go +++ b/das/memory_backed_storage_service.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" ) @@ -53,16 +53,6 @@ func (m *MemoryBackedStorageService) Put(ctx context.Context, data []byte, expir return nil } -func (m *MemoryBackedStorageService) putKeyValue(ctx context.Context, key common.Hash, value []byte) error { - m.rwmutex.Lock() - defer m.rwmutex.Unlock() - if m.closed { - return ErrClosed - } - m.contents[key] = append([]byte{}, value...) - return nil -} - func (m *MemoryBackedStorageService) Sync(ctx context.Context) error { m.rwmutex.RLock() defer m.rwmutex.RUnlock() @@ -79,8 +69,8 @@ func (m *MemoryBackedStorageService) Close(ctx context.Context) error { return nil } -func (m *MemoryBackedStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { - return arbstate.KeepForever, nil +func (m *MemoryBackedStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { + return daprovider.KeepForever, nil } func (m *MemoryBackedStorageService) String() string { diff --git a/das/panic_wrapper.go b/das/panic_wrapper.go index 7a15f6bec..3530cb651 100644 --- a/das/panic_wrapper.go +++ b/das/panic_wrapper.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) type WriterPanicWrapper struct { @@ -26,8 +26,8 @@ func (w *WriterPanicWrapper) String() string { return fmt.Sprintf("WriterPanicWrapper{%v}", w.DataAvailabilityServiceWriter) } -func (w *WriterPanicWrapper) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) { - cert, err := w.DataAvailabilityServiceWriter.Store(ctx, message, timeout, sig) +func (w *WriterPanicWrapper) Store(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) { + cert, err := w.DataAvailabilityServiceWriter.Store(ctx, message, timeout) if err != nil { panic(fmt.Sprintf("panic wrapper Store: %v", err)) } diff --git a/das/read_limited.go b/das/read_limited.go index 74d6d5358..5ef0335d5 100644 --- a/das/read_limited.go +++ b/das/read_limited.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) // These classes are wrappers implementing das.StorageService and das.DataAvailabilityService. @@ -16,12 +16,12 @@ import ( // it is a programming error in the code setting up the node or daserver if a non-writeable object // is used in a writeable context. -func NewReadLimitedStorageService(reader arbstate.DataAvailabilityReader) *readLimitedStorageService { +func NewReadLimitedStorageService(reader daprovider.DASReader) *readLimitedStorageService { return &readLimitedStorageService{reader} } type readLimitedStorageService struct { - arbstate.DataAvailabilityReader + daprovider.DASReader } func (s *readLimitedStorageService) Put(ctx context.Context, data []byte, expiration uint64) error { @@ -37,22 +37,22 @@ func (s *readLimitedStorageService) Close(ctx context.Context) error { } func (s *readLimitedStorageService) String() string { - return fmt.Sprintf("readLimitedStorageService(%v)", s.DataAvailabilityReader) + return fmt.Sprintf("readLimitedStorageService(%v)", s.DASReader) } type readLimitedDataAvailabilityService struct { - arbstate.DataAvailabilityReader + daprovider.DASReader } -func NewReadLimitedDataAvailabilityService(da arbstate.DataAvailabilityReader) *readLimitedDataAvailabilityService { +func NewReadLimitedDataAvailabilityService(da daprovider.DASReader) *readLimitedDataAvailabilityService { return &readLimitedDataAvailabilityService{da} } -func (*readLimitedDataAvailabilityService) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) { +func (*readLimitedDataAvailabilityService) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*daprovider.DataAvailabilityCertificate, error) { panic("Logic error: readLimitedDataAvailabilityService.Store shouldn't be called.") } func (s *readLimitedDataAvailabilityService) String() string { - return fmt.Sprintf("ReadLimitedDataAvailabilityService(%v)", s.DataAvailabilityReader) + return fmt.Sprintf("ReadLimitedDataAvailabilityService(%v)", s.DASReader) } diff --git a/das/reader_aggregator_strategies.go b/das/reader_aggregator_strategies.go index 855be5e31..d20760bd5 100644 --- a/das/reader_aggregator_strategies.go +++ b/das/reader_aggregator_strategies.go @@ -10,30 +10,30 @@ import ( "sync" "sync/atomic" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) var ErrNoReadersResponded = errors.New("no DAS readers responded successfully") type aggregatorStrategy interface { newInstance() aggregatorStrategyInstance - update([]arbstate.DataAvailabilityReader, map[arbstate.DataAvailabilityReader]readerStats) + update([]daprovider.DASReader, map[daprovider.DASReader]readerStats) } type abstractAggregatorStrategy struct { sync.RWMutex - readers []arbstate.DataAvailabilityReader - stats map[arbstate.DataAvailabilityReader]readerStats + readers []daprovider.DASReader + stats map[daprovider.DASReader]readerStats } -func (s *abstractAggregatorStrategy) update(readers []arbstate.DataAvailabilityReader, stats map[arbstate.DataAvailabilityReader]readerStats) { +func (s *abstractAggregatorStrategy) update(readers []daprovider.DASReader, stats map[daprovider.DASReader]readerStats) { s.Lock() defer s.Unlock() - s.readers = make([]arbstate.DataAvailabilityReader, len(readers)) + s.readers = make([]daprovider.DASReader, len(readers)) copy(s.readers, readers) - s.stats = make(map[arbstate.DataAvailabilityReader]readerStats) + s.stats = make(map[daprovider.DASReader]readerStats) for k, v := range stats { s.stats[k] = v } @@ -51,11 +51,11 @@ type simpleExploreExploitStrategy struct { func (s *simpleExploreExploitStrategy) newInstance() aggregatorStrategyInstance { iterations := atomic.AddUint32(&s.iterations, 1) - readerSets := make([][]arbstate.DataAvailabilityReader, 0) + readerSets := make([][]daprovider.DASReader, 0) s.RLock() defer s.RUnlock() - readers := make([]arbstate.DataAvailabilityReader, len(s.readers)) + readers := make([]daprovider.DASReader, len(s.readers)) copy(readers, s.readers) if iterations%(s.exploreIterations+s.exploitIterations) < s.exploreIterations { @@ -70,7 +70,7 @@ func (s *simpleExploreExploitStrategy) newInstance() aggregatorStrategyInstance } for i, maxTake := 0, 1; i < len(readers); maxTake = maxTake * 2 { - readerSet := make([]arbstate.DataAvailabilityReader, 0, maxTake) + readerSet := make([]daprovider.DASReader, 0, maxTake) for taken := 0; taken < maxTake && i < len(readers); i, taken = i+1, taken+1 { readerSet = append(readerSet, readers[i]) } @@ -91,7 +91,7 @@ func (s *testingSequentialStrategy) newInstance() aggregatorStrategyInstance { si := basicStrategyInstance{} for _, reader := range s.readers { - si.readerSets = append(si.readerSets, []arbstate.DataAvailabilityReader{reader}) + si.readerSets = append(si.readerSets, []daprovider.DASReader{reader}) } return &si @@ -99,14 +99,14 @@ func (s *testingSequentialStrategy) newInstance() aggregatorStrategyInstance { // Instance of a strategy that returns readers in an order according to the strategy type aggregatorStrategyInstance interface { - nextReaders() []arbstate.DataAvailabilityReader + nextReaders() []daprovider.DASReader } type basicStrategyInstance struct { - readerSets [][]arbstate.DataAvailabilityReader + readerSets [][]daprovider.DASReader } -func (si *basicStrategyInstance) nextReaders() []arbstate.DataAvailabilityReader { +func (si *basicStrategyInstance) nextReaders() []daprovider.DASReader { if len(si.readerSets) == 0 { return nil } diff --git a/das/reader_aggregator_strategies_test.go b/das/reader_aggregator_strategies_test.go index 987bc0893..cdb85b25e 100644 --- a/das/reader_aggregator_strategies_test.go +++ b/das/reader_aggregator_strategies_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) type dummyReader struct { @@ -26,13 +26,13 @@ func (*dummyReader) HealthCheck(context.Context) error { return errors.New("not implemented") } -func (*dummyReader) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (*dummyReader) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { return -1, errors.New("not implemented") } func TestDAS_SimpleExploreExploit(t *testing.T) { - readers := []arbstate.DataAvailabilityReader{&dummyReader{0}, &dummyReader{1}, &dummyReader{2}, &dummyReader{3}, &dummyReader{4}, &dummyReader{5}} - stats := make(map[arbstate.DataAvailabilityReader]readerStats) + readers := []daprovider.DASReader{&dummyReader{0}, &dummyReader{1}, &dummyReader{2}, &dummyReader{3}, &dummyReader{4}, &dummyReader{5}} + stats := make(map[daprovider.DASReader]readerStats) stats[readers[0]] = []readerStat{ // weighted avg 10s {10 * time.Second, true}, } @@ -57,7 +57,7 @@ func TestDAS_SimpleExploreExploit(t *testing.T) { {8 * time.Second, true}, } - expectedOrdering := []arbstate.DataAvailabilityReader{readers[1], readers[2], readers[5], readers[4], readers[0], readers[3]} + expectedOrdering := []daprovider.DASReader{readers[1], readers[2], readers[5], readers[4], readers[0], readers[3]} expectedExploreIterations, expectedExploitIterations := uint32(5), uint32(5) strategy := simpleExploreExploitStrategy{ @@ -66,7 +66,7 @@ func TestDAS_SimpleExploreExploit(t *testing.T) { } strategy.update(readers, stats) - checkMatch := func(expected, was []arbstate.DataAvailabilityReader, doMatch bool) { + checkMatch := func(expected, was []daprovider.DASReader, doMatch bool) { if len(expected) != len(was) { Fail(t, fmt.Sprintf("Incorrect number of nextReaders %d, expected %d", len(was), len(expected))) } diff --git a/das/redis_storage_service.go b/das/redis_storage_service.go index 3449a8e78..210d5cb2d 100644 --- a/das/redis_storage_service.go +++ b/das/redis_storage_service.go @@ -13,7 +13,7 @@ import ( "golang.org/x/crypto/sha3" "github.com/go-redis/redis/v8" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" "github.com/offchainlabs/nitro/util/redisutil" @@ -24,12 +24,10 @@ import ( ) type RedisConfig struct { - Enable bool `koanf:"enable"` - Url string `koanf:"url"` - Expiration time.Duration `koanf:"expiration"` - KeyConfig string `koanf:"key-config"` - SyncFromStorageService bool `koanf:"sync-from-storage-service"` - SyncToStorageService bool `koanf:"sync-to-storage-service"` + Enable bool `koanf:"enable"` + Url string `koanf:"url"` + Expiration time.Duration `koanf:"expiration"` + KeyConfig string `koanf:"key-config"` } var DefaultRedisConfig = RedisConfig{ @@ -43,8 +41,6 @@ func RedisConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".url", DefaultRedisConfig.Url, "Redis url") f.Duration(prefix+".expiration", DefaultRedisConfig.Expiration, "Redis expiration") f.String(prefix+".key-config", DefaultRedisConfig.KeyConfig, "Redis key config") - f.Bool(prefix+".sync-from-storage-service", DefaultRedisConfig.SyncFromStorageService, "enable Redis to be used as a source for regular sync storage") - f.Bool(prefix+".sync-to-storage-service", DefaultRedisConfig.SyncToStorageService, "enable Redis to be used as a sink for regular sync storage") } type RedisStorageService struct { @@ -139,17 +135,6 @@ func (rs *RedisStorageService) Put(ctx context.Context, value []byte, timeout ui return err } -func (rs *RedisStorageService) putKeyValue(ctx context.Context, key common.Hash, value []byte) error { - // Expiration is set to zero here, since we want to keep the index inserted for iterable storage forever. - err := rs.client.Set( - ctx, string(key.Bytes()), rs.signMessage(value), 0, - ).Err() - if err != nil { - log.Error("das.RedisStorageService.putKeyValue", "err", err) - } - return err -} - func (rs *RedisStorageService) Sync(ctx context.Context) error { return rs.baseStorageService.Sync(ctx) } @@ -162,7 +147,7 @@ func (rs *RedisStorageService) Close(ctx context.Context) error { return rs.baseStorageService.Close(ctx) } -func (rs *RedisStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (rs *RedisStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { return rs.baseStorageService.ExpirationPolicy(ctx) } diff --git a/das/redundant_storage_service.go b/das/redundant_storage_service.go index 74d32bd81..3158d2807 100644 --- a/das/redundant_storage_service.go +++ b/das/redundant_storage_service.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/util/pretty" ) @@ -121,7 +121,7 @@ func (r *RedundantStorageService) Close(ctx context.Context) error { return anyError } -func (r *RedundantStorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (r *RedundantStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { // If at least one inner service has KeepForever, // then whole redundant service can serve after timeout. @@ -132,20 +132,20 @@ func (r *RedundantStorageService) ExpirationPolicy(ctx context.Context) (arbstat // If no inner service has KeepForever, DiscardAfterArchiveTimeout, // but at least one inner service has DiscardAfterDataTimeout, // then whole redundant service can serve till data timeout. - var res arbstate.ExpirationPolicy = -1 + var res daprovider.ExpirationPolicy = -1 for _, serv := range r.innerServices { expirationPolicy, err := serv.ExpirationPolicy(ctx) if err != nil { return -1, err } switch expirationPolicy { - case arbstate.KeepForever: - return arbstate.KeepForever, nil - case arbstate.DiscardAfterArchiveTimeout: - res = arbstate.DiscardAfterArchiveTimeout - case arbstate.DiscardAfterDataTimeout: - if res != arbstate.DiscardAfterArchiveTimeout { - res = arbstate.DiscardAfterDataTimeout + case daprovider.KeepForever: + return daprovider.KeepForever, nil + case daprovider.DiscardAfterArchiveTimeout: + res = daprovider.DiscardAfterArchiveTimeout + case daprovider.DiscardAfterDataTimeout: + if res != daprovider.DiscardAfterArchiveTimeout { + res = daprovider.DiscardAfterDataTimeout } } } diff --git a/das/regular_sync_storage_test.go b/das/regular_sync_storage_test.go deleted file mode 100644 index 5fed7a90b..000000000 --- a/das/regular_sync_storage_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -package das - -import ( - "bytes" - "context" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - - "github.com/offchainlabs/nitro/das/dastree" -) - -func TestRegularSyncStorage(t *testing.T) { - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - syncFromStorageService := []*IterableStorageService{ - NewIterableStorageService(ConvertStorageServiceToIterationCompatibleStorageService(NewMemoryBackedStorageService(ctx))), - NewIterableStorageService(ConvertStorageServiceToIterationCompatibleStorageService(NewMemoryBackedStorageService(ctx))), - } - syncToStorageService := []StorageService{ - NewMemoryBackedStorageService(ctx), - NewMemoryBackedStorageService(ctx), - } - - regularSyncStorage := NewRegularlySyncStorage( - syncFromStorageService, - syncToStorageService, RegularSyncStorageConfig{ - Enable: true, - SyncInterval: 100 * time.Millisecond, - }) - - val := [][]byte{ - []byte("The first value"), - []byte("The second value"), - []byte("The third value"), - []byte("The forth value"), - } - valKey := []common.Hash{ - dastree.Hash(val[0]), - dastree.Hash(val[1]), - dastree.Hash(val[2]), - dastree.Hash(val[3]), - } - - reqCtx := context.Background() - timeout := uint64(time.Now().Add(time.Hour).Unix()) - for i := 0; i < 2; i++ { - for j := 0; j < 2; j++ { - err := syncFromStorageService[i].Put(reqCtx, val[j], timeout) - Require(t, err) - } - } - - regularSyncStorage.Start(ctx) - time.Sleep(300 * time.Millisecond) - - for i := 0; i < 2; i++ { - for j := 2; j < 4; j++ { - err := syncFromStorageService[i].Put(reqCtx, val[j], timeout) - Require(t, err) - } - } - - time.Sleep(300 * time.Millisecond) - - for i := 0; i < 2; i++ { - for j := 0; j < 4; j++ { - v, err := syncToStorageService[i].GetByHash(reqCtx, valKey[j]) - Require(t, err) - if !bytes.Equal(v, val[j]) { - t.Fatal(v, val[j]) - } - } - } -} diff --git a/das/regularly_sync_storage.go b/das/regularly_sync_storage.go deleted file mode 100644 index c6b8ed5ea..000000000 --- a/das/regularly_sync_storage.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -package das - -import ( - "context" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - - "github.com/offchainlabs/nitro/util/stopwaiter" - - flag "github.com/spf13/pflag" -) - -type RegularSyncStorageConfig struct { - Enable bool `koanf:"enable"` - SyncInterval time.Duration `koanf:"sync-interval"` -} - -var DefaultRegularSyncStorageConfig = RegularSyncStorageConfig{ - Enable: false, - SyncInterval: 5 * time.Minute, -} - -func RegularSyncStorageConfigAddOptions(prefix string, f *flag.FlagSet) { - f.Bool(prefix+".enable", DefaultRegularSyncStorageConfig.Enable, "enable regular storage syncing") - f.Duration(prefix+".sync-interval", DefaultRegularSyncStorageConfig.SyncInterval, "interval for running regular storage sync") -} - -// A RegularlySyncStorage is used to sync data from syncFromStorageServices to -// all the syncToStorageServices at regular intervals. -// (Only newly added data since the last sync is copied over.) -type RegularlySyncStorage struct { - stopwaiter.StopWaiter - syncFromStorageServices []*IterableStorageService - syncToStorageServices []StorageService - lastSyncedHashOfEachSyncFromStorageService map[*IterableStorageService]common.Hash - syncInterval time.Duration -} - -func NewRegularlySyncStorage(syncFromStorageServices []*IterableStorageService, syncToStorageServices []StorageService, conf RegularSyncStorageConfig) *RegularlySyncStorage { - lastSyncedHashOfEachSyncFromStorageService := make(map[*IterableStorageService]common.Hash) - for _, syncFrom := range syncFromStorageServices { - lastSyncedHashOfEachSyncFromStorageService[syncFrom] = syncFrom.DefaultBegin() - } - return &RegularlySyncStorage{ - syncFromStorageServices: syncFromStorageServices, - syncToStorageServices: syncToStorageServices, - lastSyncedHashOfEachSyncFromStorageService: lastSyncedHashOfEachSyncFromStorageService, - syncInterval: conf.SyncInterval, - } -} - -func (r *RegularlySyncStorage) Start(ctx context.Context) { - // Start thread for regular sync - r.StopWaiter.Start(ctx, r) - r.CallIteratively(r.syncAllStorages) -} - -func (r *RegularlySyncStorage) syncAllStorages(ctx context.Context) time.Duration { - for syncFrom, lastSyncedHash := range r.lastSyncedHashOfEachSyncFromStorageService { - end := syncFrom.End(ctx) - if (end == common.Hash{}) { - continue - } - - syncHash := lastSyncedHash - for syncHash != end { - syncHash = syncFrom.Next(ctx, syncHash) - data, err := syncFrom.GetByHash(ctx, syncHash) - if err != nil { - continue - } - expirationTime, err := syncFrom.GetExpirationTime(ctx, syncHash) - if err != nil { - continue - } - for _, syncTo := range r.syncToStorageServices { - _, err = syncTo.GetByHash(ctx, syncHash) - if err == nil { - continue - } - - if err = syncTo.Put(ctx, data, expirationTime); err != nil { - log.Error("Error while running regular storage sync", "err", err) - } - } - } - r.lastSyncedHashOfEachSyncFromStorageService[syncFrom] = end - } - return r.syncInterval -} diff --git a/das/restful_client.go b/das/restful_client.go index 7d757c6bb..b65426e7c 100644 --- a/das/restful_client.go +++ b/das/restful_client.go @@ -14,11 +14,11 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" ) -// RestfulDasClient implements DataAvailabilityReader +// RestfulDasClient implements daprovider.DASReader type RestfulDasClient struct { url string } @@ -65,7 +65,7 @@ func (c *RestfulDasClient) GetByHash(ctx context.Context, hash common.Hash) ([]b return nil, err } if !dastree.ValidHash(hash, decodedBytes) { - return nil, arbstate.ErrHashMismatch + return nil, daprovider.ErrHashMismatch } return decodedBytes, nil @@ -82,7 +82,7 @@ func (c *RestfulDasClient) HealthCheck(ctx context.Context) error { return nil } -func (c *RestfulDasClient) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (c *RestfulDasClient) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { res, err := http.Get(c.url + expirationPolicyRequestPath) if err != nil { return -1, err @@ -101,5 +101,5 @@ func (c *RestfulDasClient) ExpirationPolicy(ctx context.Context) (arbstate.Expir return -1, err } - return arbstate.StringToExpirationPolicy(response.ExpirationPolicy) + return daprovider.StringToExpirationPolicy(response.ExpirationPolicy) } diff --git a/das/restful_server.go b/das/restful_server.go index 5c5e82e82..b1607729e 100644 --- a/das/restful_server.go +++ b/das/restful_server.go @@ -17,7 +17,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/util/pretty" ) @@ -32,13 +32,13 @@ var ( type RestfulDasServer struct { server *http.Server - daReader arbstate.DataAvailabilityReader + daReader daprovider.DASReader daHealthChecker DataAvailabilityServiceHealthChecker httpServerExitedChan chan interface{} httpServerError error } -func NewRestfulDasServer(address string, port uint64, restServerTimeouts genericconf.HTTPServerTimeoutConfig, daReader arbstate.DataAvailabilityReader, daHealthChecker DataAvailabilityServiceHealthChecker) (*RestfulDasServer, error) { +func NewRestfulDasServer(address string, port uint64, restServerTimeouts genericconf.HTTPServerTimeoutConfig, daReader daprovider.DASReader, daHealthChecker DataAvailabilityServiceHealthChecker) (*RestfulDasServer, error) { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", address, port)) if err != nil { return nil, err @@ -46,7 +46,7 @@ func NewRestfulDasServer(address string, port uint64, restServerTimeouts generic return NewRestfulDasServerOnListener(listener, restServerTimeouts, daReader, daHealthChecker) } -func NewRestfulDasServerOnListener(listener net.Listener, restServerTimeouts genericconf.HTTPServerTimeoutConfig, daReader arbstate.DataAvailabilityReader, daHealthChecker DataAvailabilityServiceHealthChecker) (*RestfulDasServer, error) { +func NewRestfulDasServerOnListener(listener net.Listener, restServerTimeouts genericconf.HTTPServerTimeoutConfig, daReader daprovider.DASReader, daHealthChecker DataAvailabilityServiceHealthChecker) (*RestfulDasServer, error) { ret := &RestfulDasServer{ daReader: daReader, diff --git a/das/rpc_aggregator.go b/das/rpc_aggregator.go index 134c4229c..7e363c617 100644 --- a/das/rpc_aggregator.go +++ b/das/rpc_aggregator.go @@ -12,10 +12,11 @@ import ( "math/bits" "net/url" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util/metricsutil" + "github.com/offchainlabs/nitro/util/signature" "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/arbutil" @@ -27,31 +28,31 @@ type BackendConfig struct { SignerMask uint64 `json:"signermask"` } -func NewRPCAggregator(ctx context.Context, config DataAvailabilityConfig) (*Aggregator, error) { - services, err := ParseServices(config.RPCAggregator) +func NewRPCAggregator(ctx context.Context, config DataAvailabilityConfig, signer signature.DataSignerFunc) (*Aggregator, error) { + services, err := ParseServices(config.RPCAggregator, signer) if err != nil { return nil, err } return NewAggregator(ctx, config, services) } -func NewRPCAggregatorWithL1Info(config DataAvailabilityConfig, l1client arbutil.L1Interface, seqInboxAddress common.Address) (*Aggregator, error) { - services, err := ParseServices(config.RPCAggregator) +func NewRPCAggregatorWithL1Info(config DataAvailabilityConfig, l1client arbutil.L1Interface, seqInboxAddress common.Address, signer signature.DataSignerFunc) (*Aggregator, error) { + services, err := ParseServices(config.RPCAggregator, signer) if err != nil { return nil, err } return NewAggregatorWithL1Info(config, services, l1client, seqInboxAddress) } -func NewRPCAggregatorWithSeqInboxCaller(config DataAvailabilityConfig, seqInboxCaller *bridgegen.SequencerInboxCaller) (*Aggregator, error) { - services, err := ParseServices(config.RPCAggregator) +func NewRPCAggregatorWithSeqInboxCaller(config DataAvailabilityConfig, seqInboxCaller *bridgegen.SequencerInboxCaller, signer signature.DataSignerFunc) (*Aggregator, error) { + services, err := ParseServices(config.RPCAggregator, signer) if err != nil { return nil, err } return NewAggregatorWithSeqInboxCaller(config, services, seqInboxCaller) } -func ParseServices(config AggregatorConfig) ([]ServiceDetails, error) { +func ParseServices(config AggregatorConfig, signer signature.DataSignerFunc) ([]ServiceDetails, error) { var cs []BackendConfig err := json.Unmarshal([]byte(config.Backends), &cs) if err != nil { @@ -67,7 +68,7 @@ func ParseServices(config AggregatorConfig) ([]ServiceDetails, error) { } metricName := metricsutil.CanonicalizeMetricName(url.Hostname()) - service, err := NewDASRPCClient(b.URL) + service, err := NewDASRPCClient(b.URL, signer, config.MaxStoreChunkBodySize) if err != nil { return nil, err } @@ -102,7 +103,7 @@ func KeysetHashFromServices(services []ServiceDetails, assumedHonest uint64) ([3 return [32]byte{}, nil, errors.New("at least two signers share a mask") } - keyset := &arbstate.DataAvailabilityKeyset{ + keyset := &daprovider.DataAvailabilityKeyset{ AssumedHonest: uint64(assumedHonest), PubKeys: pubKeys, } diff --git a/das/rpc_test.go b/das/rpc_test.go index 044ba597b..d3c99e636 100644 --- a/das/rpc_test.go +++ b/das/rpc_test.go @@ -7,13 +7,17 @@ import ( "bytes" "context" "encoding/base64" + "encoding/hex" "encoding/json" "net" + "sync" "testing" "time" + "github.com/ethereum/go-ethereum/crypto" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/testhelpers" ) @@ -24,7 +28,9 @@ func blsPubToBase64(pubkey *blsSignatures.PublicKey) string { return string(encodedPubkey) } -func TestRPC(t *testing.T) { +func testRpcImpl(t *testing.T, size, times int, concurrent bool) { + // enableLogging() + ctx := context.Background() lis, err := net.Listen("tcp", "localhost:0") testhelpers.RequireImpl(t, err) @@ -46,16 +52,21 @@ func TestRPC(t *testing.T) { RequestTimeout: 5 * time.Second, } - var syncFromStorageServices []*IterableStorageService - var syncToStorageServices []StorageService - storageService, lifecycleManager, err := CreatePersistentStorageService(ctx, &config, &syncFromStorageServices, &syncToStorageServices) + storageService, lifecycleManager, err := CreatePersistentStorageService(ctx, &config) testhelpers.RequireImpl(t, err) defer lifecycleManager.StopAndWaitUntil(time.Second) - privKey, err := config.Key.BLSPrivKey() + localDas, err := NewSignAfterStoreDASWriter(ctx, config, storageService) testhelpers.RequireImpl(t, err) - localDas, err := NewSignAfterStoreDASWriterWithSeqInboxCaller(privKey, nil, storageService, "") + + testPrivateKey, err := crypto.GenerateKey() testhelpers.RequireImpl(t, err) - dasServer, err := StartDASRPCServerOnListener(ctx, lis, genericconf.HTTPServerTimeoutConfigDefault, storageService, localDas, storageService) + + signatureVerifier, err := NewSignatureVerifierWithSeqInboxCaller(nil, "0x"+hex.EncodeToString(crypto.FromECDSAPub(&testPrivateKey.PublicKey))) + testhelpers.RequireImpl(t, err) + signer := signature.DataSignerFromPrivateKey(testPrivateKey) + + dasServer, err := StartDASRPCServerOnListener(ctx, lis, genericconf.HTTPServerTimeoutConfigDefault, genericconf.HTTPServerBodyLimitDefault, storageService, localDas, storageService, signatureVerifier) + defer func() { if err := dasServer.Shutdown(ctx); err != nil { panic(err) @@ -72,29 +83,67 @@ func TestRPC(t *testing.T) { testhelpers.RequireImpl(t, err) aggConf := DataAvailabilityConfig{ RPCAggregator: AggregatorConfig{ - AssumedHonest: 1, - Backends: string(backendsJsonByte), + AssumedHonest: 1, + Backends: string(backendsJsonByte), + MaxStoreChunkBodySize: (chunkSize * 2) + len(sendChunkJSONBoilerplate), }, - RequestTimeout: 5 * time.Second, + RequestTimeout: time.Minute, } - rpcAgg, err := NewRPCAggregatorWithSeqInboxCaller(aggConf, nil) + rpcAgg, err := NewRPCAggregatorWithSeqInboxCaller(aggConf, nil, signer) testhelpers.RequireImpl(t, err) - msg := testhelpers.RandomizeSlice(make([]byte, 100)) - cert, err := rpcAgg.Store(ctx, msg, 0, nil) - testhelpers.RequireImpl(t, err) + var wg sync.WaitGroup + runStore := func() { + defer wg.Done() + msg := testhelpers.RandomizeSlice(make([]byte, size)) + cert, err := rpcAgg.Store(ctx, msg, 0) + testhelpers.RequireImpl(t, err) - retrievedMessage, err := storageService.GetByHash(ctx, cert.DataHash) - testhelpers.RequireImpl(t, err) + retrievedMessage, err := storageService.GetByHash(ctx, cert.DataHash) + testhelpers.RequireImpl(t, err) + + if !bytes.Equal(msg, retrievedMessage) { + testhelpers.FailImpl(t, "failed to retrieve correct message") + } + + retrievedMessage, err = storageService.GetByHash(ctx, cert.DataHash) + testhelpers.RequireImpl(t, err) - if !bytes.Equal(msg, retrievedMessage) { - testhelpers.FailImpl(t, "failed to retrieve correct message") + if !bytes.Equal(msg, retrievedMessage) { + testhelpers.FailImpl(t, "failed to getByHash correct message") + } } - retrievedMessage, err = storageService.GetByHash(ctx, cert.DataHash) - testhelpers.RequireImpl(t, err) + for i := 0; i < times; i++ { + wg.Add(1) + if concurrent { + go runStore() + } else { + runStore() + } + } + + wg.Wait() +} + +const chunkSize = 512 * 1024 - if !bytes.Equal(msg, retrievedMessage) { - testhelpers.FailImpl(t, "failed to getByHash correct message") +func TestRPCStore(t *testing.T) { + for _, tc := range []struct { + desc string + totalSize, times int + concurrent bool + leagcyAPIOnly bool + }{ + {desc: "small store", totalSize: 100, times: 1, concurrent: false}, + {desc: "chunked store - last chunk full", totalSize: chunkSize * 20, times: 10, concurrent: true}, + {desc: "chunked store - last chunk not full", totalSize: chunkSize*31 + 123, times: 10, concurrent: true}, + {desc: "chunked store - overflow cache - sequential", totalSize: chunkSize * 3, times: 15, concurrent: false}, + {desc: "new client falls back to old api for old server", totalSize: (5*1024*1024)/2 - len(sendChunkJSONBoilerplate) - 100 /* geth counts headers too */, times: 5, concurrent: true, leagcyAPIOnly: true}, + } { + t.Run(tc.desc, func(t *testing.T) { + legacyDASStoreAPIOnly = tc.leagcyAPIOnly + testRpcImpl(t, tc.totalSize, tc.times, tc.concurrent) + }) } } diff --git a/das/s3_storage_service.go b/das/s3_storage_service.go index 1a3ae9411..a1de200c5 100644 --- a/das/s3_storage_service.go +++ b/das/s3_storage_service.go @@ -15,7 +15,7 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" @@ -34,15 +34,13 @@ type S3Downloader interface { } type S3StorageServiceConfig struct { - Enable bool `koanf:"enable"` - AccessKey string `koanf:"access-key"` - Bucket string `koanf:"bucket"` - ObjectPrefix string `koanf:"object-prefix"` - Region string `koanf:"region"` - SecretKey string `koanf:"secret-key"` - DiscardAfterTimeout bool `koanf:"discard-after-timeout"` - SyncFromStorageService bool `koanf:"sync-from-storage-service"` - SyncToStorageService bool `koanf:"sync-to-storage-service"` + Enable bool `koanf:"enable"` + AccessKey string `koanf:"access-key"` + Bucket string `koanf:"bucket"` + ObjectPrefix string `koanf:"object-prefix"` + Region string `koanf:"region"` + SecretKey string `koanf:"secret-key"` + DiscardAfterTimeout bool `koanf:"discard-after-timeout"` } var DefaultS3StorageServiceConfig = S3StorageServiceConfig{} @@ -55,8 +53,6 @@ func S3ConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".region", DefaultS3StorageServiceConfig.Region, "S3 region") f.String(prefix+".secret-key", DefaultS3StorageServiceConfig.SecretKey, "S3 secret key") f.Bool(prefix+".discard-after-timeout", DefaultS3StorageServiceConfig.DiscardAfterTimeout, "discard data after its expiry timeout") - f.Bool(prefix+".sync-from-storage-service", DefaultRedisConfig.SyncFromStorageService, "enable s3 to be used as a source for regular sync storage") - f.Bool(prefix+".sync-to-storage-service", DefaultRedisConfig.SyncToStorageService, "enable s3 to be used as a sink for regular sync storage") } type S3StorageService struct { @@ -125,18 +121,6 @@ func (s3s *S3StorageService) Put(ctx context.Context, value []byte, timeout uint return err } -func (s3s *S3StorageService) putKeyValue(ctx context.Context, key common.Hash, value []byte) error { - putObjectInput := s3.PutObjectInput{ - Bucket: aws.String(s3s.bucket), - Key: aws.String(s3s.objectPrefix + EncodeStorageServiceKey(key)), - Body: bytes.NewReader(value)} - _, err := s3s.uploader.Upload(ctx, &putObjectInput) - if err != nil { - log.Error("das.S3StorageService.Store", "err", err) - } - return err -} - func (s3s *S3StorageService) Sync(ctx context.Context) error { return nil } @@ -145,11 +129,11 @@ func (s3s *S3StorageService) Close(ctx context.Context) error { return nil } -func (s3s *S3StorageService) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (s3s *S3StorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { if s3s.discardAfterTimeout { - return arbstate.DiscardAfterDataTimeout, nil + return daprovider.DiscardAfterDataTimeout, nil } - return arbstate.KeepForever, nil + return daprovider.KeepForever, nil } func (s3s *S3StorageService) String() string { diff --git a/das/sign_after_store_das_writer.go b/das/sign_after_store_das_writer.go index 50c4ee9ae..0e31d30ae 100644 --- a/das/sign_after_store_das_writer.go +++ b/das/sign_after_store_das_writer.go @@ -6,7 +6,6 @@ package das import ( "bytes" "context" - "encoding/hex" "errors" "fmt" "os" @@ -15,14 +14,11 @@ import ( flag "github.com/spf13/pflag" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/das/dastree" - "github.com/offchainlabs/nitro/solgen/go/bridgegen" - "github.com/offchainlabs/nitro/util/contracts" "github.com/offchainlabs/nitro/util/pretty" ) @@ -67,22 +63,12 @@ func KeyConfigAddOptions(prefix string, f *flag.FlagSet) { // // 1) SignAfterStoreDASWriter.Store(...) assembles the returned hash into a // DataAvailabilityCertificate and signs it with its BLS private key. -// -// 2) If Sequencer Inbox contract details are provided when a SignAfterStoreDASWriter is -// constructed, calls to Store(...) will try to verify the passed-in data's signature -// is from the batch poster. If the contract details are not provided, then the -// signature is not checked, which is useful for testing. type SignAfterStoreDASWriter struct { privKey blsSignatures.PrivateKey pubKey *blsSignatures.PublicKey keysetHash [32]byte keysetBytes []byte storageService StorageService - addrVerifier *contracts.AddressVerifier - - // Extra batch poster verifier, for local installations to have their - // own way of testing Stores. - extraBpVerifier func(message []byte, timeout uint64, sig []byte) bool } func NewSignAfterStoreDASWriter(ctx context.Context, config DataAvailabilityConfig, storageService StorageService) (*SignAfterStoreDASWriter, error) { @@ -90,40 +76,13 @@ func NewSignAfterStoreDASWriter(ctx context.Context, config DataAvailabilityConf if err != nil { return nil, err } - if config.ParentChainNodeURL == "none" { - return NewSignAfterStoreDASWriterWithSeqInboxCaller(privKey, nil, storageService, config.ExtraSignatureCheckingPublicKey) - } - l1client, err := GetL1Client(ctx, config.ParentChainConnectionAttempts, config.ParentChainNodeURL) - if err != nil { - return nil, err - } - seqInboxAddress, err := OptionalAddressFromString(config.SequencerInboxAddress) - if err != nil { - return nil, err - } - if seqInboxAddress == nil { - return NewSignAfterStoreDASWriterWithSeqInboxCaller(privKey, nil, storageService, config.ExtraSignatureCheckingPublicKey) - } - seqInboxCaller, err := bridgegen.NewSequencerInboxCaller(*seqInboxAddress, l1client) - if err != nil { - return nil, err - } - return NewSignAfterStoreDASWriterWithSeqInboxCaller(privKey, seqInboxCaller, storageService, config.ExtraSignatureCheckingPublicKey) -} - -func NewSignAfterStoreDASWriterWithSeqInboxCaller( - privKey blsSignatures.PrivateKey, - seqInboxCaller *bridgegen.SequencerInboxCaller, - storageService StorageService, - extraSignatureCheckingPublicKey string, -) (*SignAfterStoreDASWriter, error) { publicKey, err := blsSignatures.PublicKeyFromPrivateKey(privKey) if err != nil { return nil, err } - keyset := &arbstate.DataAvailabilityKeyset{ + keyset := &daprovider.DataAvailabilityKeyset{ AssumedHonest: 1, PubKeys: []blsSignatures.PublicKey{publicKey}, } @@ -136,72 +95,18 @@ func NewSignAfterStoreDASWriterWithSeqInboxCaller( return nil, err } - var addrVerifier *contracts.AddressVerifier - if seqInboxCaller != nil { - addrVerifier = contracts.NewAddressVerifier(seqInboxCaller) - } - - var extraBpVerifier func(message []byte, timeout uint64, sig []byte) bool - if extraSignatureCheckingPublicKey != "" { - var pubkey []byte - if extraSignatureCheckingPublicKey[:2] == "0x" { - pubkey, err = hex.DecodeString(extraSignatureCheckingPublicKey[2:]) - if err != nil { - return nil, err - } - } else { - pubkeyEncoded, err := os.ReadFile(extraSignatureCheckingPublicKey) - if err != nil { - return nil, err - } - pubkey, err = hex.DecodeString(string(pubkeyEncoded)) - if err != nil { - return nil, err - } - } - extraBpVerifier = func(message []byte, timeout uint64, sig []byte) bool { - if len(sig) >= 64 { - return crypto.VerifySignature(pubkey, dasStoreHash(message, timeout), sig[:64]) - } - return false - } - } - return &SignAfterStoreDASWriter{ - privKey: privKey, - pubKey: &publicKey, - keysetHash: ksHash, - keysetBytes: ksBuf.Bytes(), - storageService: storageService, - addrVerifier: addrVerifier, - extraBpVerifier: extraBpVerifier, + privKey: privKey, + pubKey: &publicKey, + keysetHash: ksHash, + keysetBytes: ksBuf.Bytes(), + storageService: storageService, }, nil } -func (d *SignAfterStoreDASWriter) Store( - ctx context.Context, message []byte, timeout uint64, sig []byte, -) (c *arbstate.DataAvailabilityCertificate, err error) { - log.Trace("das.SignAfterStoreDASWriter.Store", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0), "sig", pretty.FirstFewBytes(sig), "this", d) - var verified bool - if d.extraBpVerifier != nil { - verified = d.extraBpVerifier(message, timeout, sig) - } - - if !verified && d.addrVerifier != nil { - actualSigner, err := DasRecoverSigner(message, timeout, sig) - if err != nil { - return nil, err - } - isBatchPosterOrSequencer, err := d.addrVerifier.IsBatchPosterOrSequencer(ctx, actualSigner) - if err != nil { - return nil, err - } - if !isBatchPosterOrSequencer { - return nil, errors.New("store request not properly signed") - } - } - - c = &arbstate.DataAvailabilityCertificate{ +func (d *SignAfterStoreDASWriter) Store(ctx context.Context, message []byte, timeout uint64) (c *daprovider.DataAvailabilityCertificate, err error) { + log.Trace("das.SignAfterStoreDASWriter.Store", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0), "this", d) + c = &daprovider.DataAvailabilityCertificate{ Timeout: timeout, DataHash: dastree.Hash(message), Version: 1, diff --git a/das/signature_verifier.go b/das/signature_verifier.go new file mode 100644 index 000000000..0aa42bceb --- /dev/null +++ b/das/signature_verifier.go @@ -0,0 +1,126 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package das + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/util/contracts" +) + +// SignatureVerifier.Store will try to verify that the passed-in data's signature +// is from the batch poster, or from an injectable verification method. +type SignatureVerifier struct { + addrVerifier *contracts.AddressVerifier + + // Extra batch poster verifier, for local installations to have their + // own way of testing Stores. + extraBpVerifier func(message []byte, sig []byte, extraFields ...uint64) bool +} + +func NewSignatureVerifier(ctx context.Context, config DataAvailabilityConfig) (*SignatureVerifier, error) { + if config.ParentChainNodeURL == "none" { + return NewSignatureVerifierWithSeqInboxCaller(nil, config.ExtraSignatureCheckingPublicKey) + } + l1client, err := GetL1Client(ctx, config.ParentChainConnectionAttempts, config.ParentChainNodeURL) + if err != nil { + return nil, err + } + seqInboxAddress, err := OptionalAddressFromString(config.SequencerInboxAddress) + if err != nil { + return nil, err + } + if seqInboxAddress == nil { + return NewSignatureVerifierWithSeqInboxCaller(nil, config.ExtraSignatureCheckingPublicKey) + } + + seqInboxCaller, err := bridgegen.NewSequencerInboxCaller(*seqInboxAddress, l1client) + if err != nil { + return nil, err + } + return NewSignatureVerifierWithSeqInboxCaller(seqInboxCaller, config.ExtraSignatureCheckingPublicKey) + +} + +func NewSignatureVerifierWithSeqInboxCaller( + seqInboxCaller *bridgegen.SequencerInboxCaller, + extraSignatureCheckingPublicKey string, +) (*SignatureVerifier, error) { + var addrVerifier *contracts.AddressVerifier + if seqInboxCaller != nil { + addrVerifier = contracts.NewAddressVerifier(seqInboxCaller) + } + + var extraBpVerifier func(message []byte, sig []byte, extraFeilds ...uint64) bool + if extraSignatureCheckingPublicKey != "" { + var pubkey []byte + var err error + if extraSignatureCheckingPublicKey[:2] == "0x" { + pubkey, err = hex.DecodeString(extraSignatureCheckingPublicKey[2:]) + if err != nil { + return nil, err + } + } else { + pubkeyEncoded, err := os.ReadFile(extraSignatureCheckingPublicKey) + if err != nil { + return nil, err + } + pubkey, err = hex.DecodeString(string(pubkeyEncoded)) + if err != nil { + return nil, err + } + } + extraBpVerifier = func(message []byte, sig []byte, extraFields ...uint64) bool { + if len(sig) >= 64 { + return crypto.VerifySignature(pubkey, dasStoreHash(message, extraFields...), sig[:64]) + } + return false + } + } + + return &SignatureVerifier{ + addrVerifier: addrVerifier, + extraBpVerifier: extraBpVerifier, + }, nil + +} + +func (v *SignatureVerifier) verify( + ctx context.Context, message []byte, sig []byte, extraFields ...uint64) error { + if v.extraBpVerifier == nil && v.addrVerifier == nil { + return errors.New("no signature verification method configured") + } + + var verified bool + if v.extraBpVerifier != nil { + verified = v.extraBpVerifier(message, sig, extraFields...) + } + + if !verified && v.addrVerifier != nil { + actualSigner, err := DasRecoverSigner(message, sig, extraFields...) + if err != nil { + return err + } + verified, err = v.addrVerifier.IsBatchPosterOrSequencer(ctx, actualSigner) + if err != nil { + return err + } + } + if !verified { + return errors.New("request not properly signed") + } + return nil +} + +func (v *SignatureVerifier) String() string { + hasAddrVerifier := v.addrVerifier != nil + hasExtraBpVerifier := v.extraBpVerifier != nil + return fmt.Sprintf("SignatureVerifier{hasAddrVerifier:%v,hasExtraBpVerifier:%v}", hasAddrVerifier, hasExtraBpVerifier) +} diff --git a/das/simple_das_reader_aggregator.go b/das/simple_das_reader_aggregator.go index eb82a3383..dc6147a7e 100644 --- a/das/simple_das_reader_aggregator.go +++ b/das/simple_das_reader_aggregator.go @@ -14,7 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -80,7 +80,7 @@ func SimpleExploreExploitStrategyConfigAddOptions(prefix string, f *flag.FlagSet func NewRestfulClientAggregator(ctx context.Context, config *RestfulClientAggregatorConfig) (*SimpleDASReaderAggregator, error) { a := SimpleDASReaderAggregator{ config: config, - stats: make(map[arbstate.DataAvailabilityReader]readerStats), + stats: make(map[daprovider.DASReader]readerStats), } combinedUrls := make(map[string]bool) @@ -160,7 +160,7 @@ type readerStat struct { type readerStatMessage struct { readerStat - reader arbstate.DataAvailabilityReader + reader daprovider.DASReader } type SimpleDASReaderAggregator struct { @@ -170,8 +170,8 @@ type SimpleDASReaderAggregator struct { readersMutex sync.RWMutex // readers and stats are only to be updated by the stats goroutine - readers []arbstate.DataAvailabilityReader - stats map[arbstate.DataAvailabilityReader]readerStats + readers []daprovider.DASReader + stats map[daprovider.DASReader]readerStats strategy aggregatorStrategy @@ -199,7 +199,7 @@ func (a *SimpleDASReaderAggregator) GetByHash(ctx context.Context, hash common.H waitChan := make(chan interface{}) for _, reader := range readers { wg.Add(1) - go func(reader arbstate.DataAvailabilityReader) { + go func(reader daprovider.DASReader) { defer wg.Done() data, err := a.tryGetByHash(subCtx, hash, reader) if err != nil && errors.Is(ctx.Err(), context.Canceled) { @@ -243,7 +243,7 @@ func (a *SimpleDASReaderAggregator) GetByHash(ctx context.Context, hash common.H } func (a *SimpleDASReaderAggregator) tryGetByHash( - ctx context.Context, hash common.Hash, reader arbstate.DataAvailabilityReader, + ctx context.Context, hash common.Hash, reader daprovider.DASReader, ) ([]byte, error) { stat := readerStatMessage{reader: reader} stat.success = false @@ -278,7 +278,7 @@ func (a *SimpleDASReaderAggregator) Start(ctx context.Context) { defer a.readersMutex.Unlock() combinedUrls := a.config.Urls combinedUrls = append(combinedUrls, urls...) - combinedReaders := make(map[arbstate.DataAvailabilityReader]bool) + combinedReaders := make(map[daprovider.DASReader]bool) for _, url := range combinedUrls { reader, err := NewRestfulDasClientFromURL(url) if err != nil { @@ -286,7 +286,7 @@ func (a *SimpleDASReaderAggregator) Start(ctx context.Context) { } combinedReaders[reader] = true } - a.readers = make([]arbstate.DataAvailabilityReader, 0, len(combinedUrls)) + a.readers = make([]daprovider.DASReader, 0, len(combinedUrls)) // Update reader and add newly added stats for reader := range combinedReaders { a.readers = append(a.readers, reader) @@ -350,7 +350,7 @@ func (a *SimpleDASReaderAggregator) HealthCheck(ctx context.Context) error { return nil } -func (a *SimpleDASReaderAggregator) ExpirationPolicy(ctx context.Context) (arbstate.ExpirationPolicy, error) { +func (a *SimpleDASReaderAggregator) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { a.readersMutex.RLock() defer a.readersMutex.RUnlock() if len(a.readers) == 0 { @@ -368,7 +368,7 @@ func (a *SimpleDASReaderAggregator) ExpirationPolicy(ctx context.Context) (arbst return -1, err } if ep != expectedExpirationPolicy { - return arbstate.MixedTimeout, nil + return daprovider.MixedTimeout, nil } } return expectedExpirationPolicy, nil diff --git a/das/storage_service.go b/das/storage_service.go index 881d6fc8b..806e80dba 100644 --- a/das/storage_service.go +++ b/das/storage_service.go @@ -11,13 +11,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" ) var ErrNotFound = errors.New("not found") type StorageService interface { - arbstate.DataAvailabilityReader + daprovider.DASReader Put(ctx context.Context, data []byte, expirationTime uint64) error Sync(ctx context.Context) error Closer diff --git a/das/store_signing.go b/das/store_signing.go index 8039774b6..eac25e48b 100644 --- a/das/store_signing.go +++ b/das/store_signing.go @@ -4,71 +4,35 @@ package das import ( - "context" "encoding/binary" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" "github.com/offchainlabs/nitro/das/dastree" - "github.com/offchainlabs/nitro/util/pretty" "github.com/offchainlabs/nitro/util/signature" ) var uniquifyingPrefix = []byte("Arbitrum Nitro DAS API Store:") -func applyDasSigner(signer signature.DataSignerFunc, data []byte, timeout uint64) ([]byte, error) { - return signer(dasStoreHash(data, timeout)) +func applyDasSigner(signer signature.DataSignerFunc, data []byte, extraFields ...uint64) ([]byte, error) { + return signer(dasStoreHash(data, extraFields...)) } -func DasRecoverSigner(data []byte, timeout uint64, sig []byte) (common.Address, error) { - pk, err := crypto.SigToPub(dasStoreHash(data, timeout), sig) +func DasRecoverSigner(data []byte, sig []byte, extraFields ...uint64) (common.Address, error) { + pk, err := crypto.SigToPub(dasStoreHash(data, extraFields...), sig) if err != nil { return common.Address{}, err } return crypto.PubkeyToAddress(*pk), nil } -func dasStoreHash(data []byte, timeout uint64) []byte { - var buf8 [8]byte - binary.BigEndian.PutUint64(buf8[:], timeout) - return dastree.HashBytes(uniquifyingPrefix, buf8[:], data) -} - -type StoreSigningDAS struct { - DataAvailabilityServiceWriter - signer signature.DataSignerFunc - addr common.Address -} +func dasStoreHash(data []byte, extraFields ...uint64) []byte { + var buf []byte -func NewStoreSigningDAS(inner DataAvailabilityServiceWriter, signer signature.DataSignerFunc) (DataAvailabilityServiceWriter, error) { - sig, err := applyDasSigner(signer, []byte{}, 0) - if err != nil { - return nil, err + for _, field := range extraFields { + buf = binary.BigEndian.AppendUint64(buf, field) } - addr, err := DasRecoverSigner([]byte{}, 0, sig) - if err != nil { - return nil, err - } - return &StoreSigningDAS{inner, signer, addr}, nil -} - -func (s *StoreSigningDAS) Store(ctx context.Context, message []byte, timeout uint64, sig []byte) (*arbstate.DataAvailabilityCertificate, error) { - log.Trace("das.StoreSigningDAS.Store(...)", "message", pretty.FirstFewBytes(message), "timeout", time.Unix(int64(timeout), 0), "sig", pretty.FirstFewBytes(sig), "this", s) - mySig, err := applyDasSigner(s.signer, message, timeout) - if err != nil { - return nil, err - } - return s.DataAvailabilityServiceWriter.Store(ctx, message, timeout, mySig) -} - -func (s *StoreSigningDAS) String() string { - return "StoreSigningDAS (" + s.SignerAddress().Hex() + " ," + s.DataAvailabilityServiceWriter.String() + ")" -} -func (s *StoreSigningDAS) SignerAddress() common.Address { - return s.addr + return dastree.HashBytes(uniquifyingPrefix, buf, data) } diff --git a/das/store_signing_test.go b/das/store_signing_test.go index 33b94f66e..a50d1c37f 100644 --- a/das/store_signing_test.go +++ b/das/store_signing_test.go @@ -25,7 +25,7 @@ func TestStoreSigning(t *testing.T) { sig, err := applyDasSigner(signer, weirdMessage, timeout) Require(t, err) - recoveredAddr, err := DasRecoverSigner(weirdMessage, timeout, sig) + recoveredAddr, err := DasRecoverSigner(weirdMessage, sig, timeout) Require(t, err) if recoveredAddr != addr { diff --git a/das/syncing_fallback_storage.go b/das/syncing_fallback_storage.go index c79cd8040..954da1067 100644 --- a/das/syncing_fallback_storage.go +++ b/das/syncing_fallback_storage.go @@ -6,7 +6,6 @@ package das import ( "context" "encoding/binary" - "errors" "fmt" "math" "math/big" @@ -20,7 +19,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util/arbmath" @@ -57,7 +56,6 @@ func init() { } type SyncToStorageConfig struct { - CheckAlreadyExists bool `koanf:"check-already-exists"` Eager bool `koanf:"eager"` EagerLowerBoundBlock uint64 `koanf:"eager-lower-bound-block"` RetentionPeriod time.Duration `koanf:"retention-period"` @@ -68,7 +66,6 @@ type SyncToStorageConfig struct { } var DefaultSyncToStorageConfig = SyncToStorageConfig{ - CheckAlreadyExists: true, Eager: false, EagerLowerBoundBlock: 0, RetentionPeriod: time.Duration(math.MaxInt64), @@ -79,7 +76,6 @@ var DefaultSyncToStorageConfig = SyncToStorageConfig{ } func SyncToStorageConfigAddOptions(prefix string, f *flag.FlagSet) { - f.Bool(prefix+".check-already-exists", DefaultSyncToStorageConfig.CheckAlreadyExists, "check if the data already exists in this DAS's storage. Must be disabled for fast sync with an IPFS backend") f.Bool(prefix+".eager", DefaultSyncToStorageConfig.Eager, "eagerly sync batch data to this DAS's storage from the rest endpoints, using L1 as the index of batch data hashes; otherwise only sync lazily") f.Uint64(prefix+".eager-lower-bound-block", DefaultSyncToStorageConfig.EagerLowerBoundBlock, "when eagerly syncing, start indexing forward from this L1 block. Only used if there is no sync state") f.Uint64(prefix+".parent-chain-blocks-per-read", DefaultSyncToStorageConfig.ParentChainBlocksPerRead, "when eagerly syncing, max l1 blocks to read per poll") @@ -94,7 +90,7 @@ type l1SyncService struct { config SyncToStorageConfig syncTo StorageService - dataSource arbstate.DataAvailabilityReader + dataSource daprovider.DASReader l1Reader *headerreader.HeaderReader inboxContract *bridgegen.SequencerInbox @@ -106,7 +102,9 @@ type l1SyncService struct { lastBatchAcc common.Hash } -const nextBlockNoFilename = "nextBlockNumber" +// The original syncing process had a bug, so the file was renamed to cause any mirrors +// in the wild to re-sync from their configured starting block number. +const nextBlockNoFilename = "nextBlockNumberV2" func readSyncStateOrDefault(syncDir string, dflt uint64) uint64 { if syncDir == "" { @@ -161,7 +159,7 @@ func writeSyncState(syncDir string, blockNr uint64) error { return os.Rename(f.Name(), path) } -func newl1SyncService(config *SyncToStorageConfig, syncTo StorageService, dataSource arbstate.DataAvailabilityReader, l1Reader *headerreader.HeaderReader, inboxAddr common.Address) (*l1SyncService, error) { +func newl1SyncService(config *SyncToStorageConfig, syncTo StorageService, dataSource daprovider.DASReader, l1Reader *headerreader.HeaderReader, inboxAddr common.Address) (*l1SyncService, error) { l1Client := l1Reader.Client() inboxContract, err := bridgegen.NewSequencerInbox(inboxAddr, l1Client) if err != nil { @@ -212,26 +210,18 @@ func (s *l1SyncService) processBatchDelivered(ctx context.Context, batchDelivere binary.BigEndian.PutUint64(header[32:40], deliveredEvent.AfterDelayedMessagesRead.Uint64()) data = append(header, data...) - preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) - if _, err = arbstate.RecoverPayloadFromDasBatch(ctx, deliveredEvent.BatchSequenceNumber.Uint64(), data, s.dataSource, preimages, arbstate.KeysetValidate); err != nil { + var payload []byte + if payload, err = daprovider.RecoverPayloadFromDasBatch(ctx, deliveredEvent.BatchSequenceNumber.Uint64(), data, s.dataSource, nil, true); err != nil { log.Error("recover payload failed", "txhash", batchDeliveredLog.TxHash, "data", data) return err } - for _, preimages := range preimages { - for hash, contents := range preimages { - var err error - if s.config.CheckAlreadyExists { - _, err = s.syncTo.GetByHash(ctx, hash) - } - if err == nil || errors.Is(err, ErrNotFound) { - if err := s.syncTo.Put(ctx, contents, storeUntil); err != nil { - return err - } - } else { - return err - } + + if payload != nil { + if err := s.syncTo.Put(ctx, payload, storeUntil); err != nil { + return err } } + seqNumber := deliveredEvent.BatchSequenceNumber if seqNumber == nil { seqNumber = common.Big0 @@ -291,7 +281,7 @@ func FindDASDataFromLog( log.Warn("BatchDelivered - no data found", "data", data) return nil, nil } - if !arbstate.IsDASMessageHeaderByte(data[0]) { + if !daprovider.IsDASMessageHeaderByte(data[0]) { log.Warn("BatchDelivered - data not DAS") return nil, nil } @@ -417,7 +407,7 @@ type SyncingFallbackStorageService struct { func NewSyncingFallbackStorageService(ctx context.Context, primary StorageService, - backup arbstate.DataAvailabilityReader, + backup daprovider.DASReader, backupHealthChecker DataAvailabilityServiceHealthChecker, l1Reader *headerreader.HeaderReader, inboxAddr common.Address, diff --git a/das/util.go b/das/util.go index d98a2687f..de266c433 100644 --- a/das/util.go +++ b/das/util.go @@ -7,11 +7,11 @@ import ( "time" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/util/pretty" ) -func logPut(store string, data []byte, timeout uint64, reader arbstate.DataAvailabilityReader, more ...interface{}) { +func logPut(store string, data []byte, timeout uint64, reader daprovider.DASReader, more ...interface{}) { if len(more) == 0 { log.Trace( store, "message", pretty.FirstFewBytes(data), "timeout", time.Unix(int64(timeout), 0), diff --git a/execution/gethexec/arb_interface.go b/execution/gethexec/arb_interface.go index 50d7dfb89..dbf9c2401 100644 --- a/execution/gethexec/arb_interface.go +++ b/execution/gethexec/arb_interface.go @@ -21,30 +21,31 @@ type TransactionPublisher interface { } type ArbInterface struct { - exec *ExecutionEngine + blockchain *core.BlockChain + node *ExecutionNode txPublisher TransactionPublisher - arbNode interface{} } -func NewArbInterface(exec *ExecutionEngine, txPublisher TransactionPublisher) (*ArbInterface, error) { +func NewArbInterface(blockchain *core.BlockChain, txPublisher TransactionPublisher) (*ArbInterface, error) { return &ArbInterface{ - exec: exec, + blockchain: blockchain, txPublisher: txPublisher, }, nil } -func (a *ArbInterface) Initialize(arbnode interface{}) { - a.arbNode = arbnode +func (a *ArbInterface) Initialize(node *ExecutionNode) { + a.node = node } func (a *ArbInterface) PublishTransaction(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { return a.txPublisher.PublishTransaction(ctx, tx, options) } +// might be used before Initialize func (a *ArbInterface) BlockChain() *core.BlockChain { - return a.exec.bc + return a.blockchain } func (a *ArbInterface) ArbNode() interface{} { - return a.arbNode + return a.node } diff --git a/execution/gethexec/block_recorder.go b/execution/gethexec/block_recorder.go index d7e702f3c..5b509b97f 100644 --- a/execution/gethexec/block_recorder.go +++ b/execution/gethexec/block_recorder.go @@ -123,7 +123,7 @@ func (r *BlockRecorder) RecordBlockCreation( var readBatchInfo []validator.BatchInfo if msg != nil { batchFetcher := func(batchNum uint64) ([]byte, error) { - data, blockHash, err := r.execEngine.streamer.FetchBatch(batchNum) + data, blockHash, err := r.execEngine.consensus.FetchBatch(ctx, batchNum) if err != nil { return nil, err } @@ -145,6 +145,7 @@ func (r *BlockRecorder) RecordBlockCreation( chaincontext, chainConfig, batchFetcher, + false, ) if err != nil { return nil, err @@ -172,6 +173,7 @@ func (r *BlockRecorder) RecordBlockCreation( BlockHash: blockHash, Preimages: preimages, BatchInfo: readBatchInfo, + UserWasms: recordingdb.UserWasms(), }, err } diff --git a/execution/gethexec/blockchain.go b/execution/gethexec/blockchain.go index 2a20c3da2..1d5060ca8 100644 --- a/execution/gethexec/blockchain.go +++ b/execution/gethexec/blockchain.go @@ -37,6 +37,7 @@ type CachingConfig struct { SnapshotRestoreGasLimit uint64 `koanf:"snapshot-restore-gas-limit"` MaxNumberOfBlocksToSkipStateSaving uint32 `koanf:"max-number-of-blocks-to-skip-state-saving"` MaxAmountOfGasToSkipStateSaving uint64 `koanf:"max-amount-of-gas-to-skip-state-saving"` + StylusLRUCache uint32 `koanf:"stylus-lru-cache"` } func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -51,6 +52,7 @@ func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { f.Uint64(prefix+".snapshot-restore-gas-limit", DefaultCachingConfig.SnapshotRestoreGasLimit, "maximum gas rolled back to recover snapshot") f.Uint32(prefix+".max-number-of-blocks-to-skip-state-saving", DefaultCachingConfig.MaxNumberOfBlocksToSkipStateSaving, "maximum number of blocks to skip state saving to persistent storage (archive node only) -- warning: this option seems to cause issues") f.Uint64(prefix+".max-amount-of-gas-to-skip-state-saving", DefaultCachingConfig.MaxAmountOfGasToSkipStateSaving, "maximum amount of gas in blocks to skip saving state to Persistent storage (archive node only) -- warning: this option seems to cause issues") + f.Uint32(prefix+".stylus-lru-cache", DefaultCachingConfig.StylusLRUCache, "initialized stylus programs to keep in LRU cache") } var DefaultCachingConfig = CachingConfig{ @@ -65,6 +67,22 @@ var DefaultCachingConfig = CachingConfig{ SnapshotRestoreGasLimit: 300_000_000_000, MaxNumberOfBlocksToSkipStateSaving: 0, MaxAmountOfGasToSkipStateSaving: 0, + StylusLRUCache: 256, +} + +var TestCachingConfig = CachingConfig{ + Archive: false, + BlockCount: 128, + BlockAge: 30 * time.Minute, + TrieTimeLimit: time.Hour, + TrieDirtyCache: 1024, + TrieCleanCache: 600, + SnapshotCache: 400, + DatabaseCache: 2048, + SnapshotRestoreGasLimit: 300_000_000_000, + MaxNumberOfBlocksToSkipStateSaving: 0, + MaxAmountOfGasToSkipStateSaving: 0, + StylusLRUCache: 0, } // TODO remove stack from parameters as it is no longer needed here diff --git a/arbnode/classicMessage.go b/execution/gethexec/classicMessage.go similarity index 99% rename from arbnode/classicMessage.go rename to execution/gethexec/classicMessage.go index f03ef5bd4..df749b98b 100644 --- a/arbnode/classicMessage.go +++ b/execution/gethexec/classicMessage.go @@ -1,7 +1,7 @@ // Copyright 2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -package arbnode +package gethexec import ( "encoding/binary" diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index 518f95f1f..95b865df5 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -1,10 +1,27 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build !wasm +// +build !wasm + package gethexec +/* +#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm +#include "arbitrator.h" +*/ +import "C" import ( + "bytes" "context" "encoding/binary" "errors" "fmt" + "os" + "path" + "runtime/pprof" + "runtime/trace" "sync" "testing" "time" @@ -15,10 +32,12 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + "github.com/google/uuid" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/util/arbmath" @@ -27,18 +46,34 @@ import ( ) var ( - baseFeeGauge = metrics.NewRegisteredGauge("arb/block/basefee", nil) - blockGasUsedHistogram = metrics.NewRegisteredHistogram("arb/block/gasused", nil, metrics.NewBoundedHistogramSample()) - txCountHistogram = metrics.NewRegisteredHistogram("arb/block/transactions/count", nil, metrics.NewBoundedHistogramSample()) - txGasUsedHistogram = metrics.NewRegisteredHistogram("arb/block/transactions/gasused", nil, metrics.NewBoundedHistogramSample()) + l1GasPriceEstimateGauge = metrics.NewRegisteredGauge("arb/l1gasprice/estimate", nil) + baseFeeGauge = metrics.NewRegisteredGauge("arb/block/basefee", nil) + blockGasUsedHistogram = metrics.NewRegisteredHistogram("arb/block/gasused", nil, metrics.NewBoundedHistogramSample()) + txCountHistogram = metrics.NewRegisteredHistogram("arb/block/transactions/count", nil, metrics.NewBoundedHistogramSample()) + txGasUsedHistogram = metrics.NewRegisteredHistogram("arb/block/transactions/gasused", nil, metrics.NewBoundedHistogramSample()) + gasUsedSinceStartupCounter = metrics.NewRegisteredCounter("arb/gas_used", nil) ) +type L1PriceDataOfMsg struct { + callDataUnits uint64 + cummulativeCallDataUnits uint64 + l1GasCharged uint64 + cummulativeL1GasCharged uint64 +} + +type L1PriceData struct { + mutex sync.RWMutex + startOfL1PriceDataCache arbutil.MessageIndex + endOfL1PriceDataCache arbutil.MessageIndex + msgToL1PriceData []L1PriceDataOfMsg +} + type ExecutionEngine struct { stopwaiter.StopWaiter - bc *core.BlockChain - streamer execution.TransactionStreamer - recorder *BlockRecorder + bc *core.BlockChain + consensus execution.FullConsensusClient + recorder *BlockRecorder resequenceChan chan []*arbostypes.MessageWithMetadata createBlocksMutex sync.Mutex @@ -52,16 +87,74 @@ type ExecutionEngine struct { reorgSequencing bool prefetchBlock bool + + cachedL1PriceData *L1PriceData +} + +func NewL1PriceData() *L1PriceData { + return &L1PriceData{ + msgToL1PriceData: []L1PriceDataOfMsg{}, + } } func NewExecutionEngine(bc *core.BlockChain) (*ExecutionEngine, error) { return &ExecutionEngine{ - bc: bc, - resequenceChan: make(chan []*arbostypes.MessageWithMetadata), - newBlockNotifier: make(chan struct{}, 1), + bc: bc, + resequenceChan: make(chan []*arbostypes.MessageWithMetadata), + newBlockNotifier: make(chan struct{}, 1), + cachedL1PriceData: NewL1PriceData(), }, nil } +func (s *ExecutionEngine) backlogCallDataUnits() uint64 { + s.cachedL1PriceData.mutex.RLock() + defer s.cachedL1PriceData.mutex.RUnlock() + + size := len(s.cachedL1PriceData.msgToL1PriceData) + if size == 0 { + return 0 + } + return (s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeCallDataUnits - + s.cachedL1PriceData.msgToL1PriceData[0].cummulativeCallDataUnits + + s.cachedL1PriceData.msgToL1PriceData[0].callDataUnits) +} + +func (s *ExecutionEngine) backlogL1GasCharged() uint64 { + s.cachedL1PriceData.mutex.RLock() + defer s.cachedL1PriceData.mutex.RUnlock() + + size := len(s.cachedL1PriceData.msgToL1PriceData) + if size == 0 { + return 0 + } + return (s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeL1GasCharged - + s.cachedL1PriceData.msgToL1PriceData[0].cummulativeL1GasCharged + + s.cachedL1PriceData.msgToL1PriceData[0].l1GasCharged) +} + +func (s *ExecutionEngine) MarkFeedStart(to arbutil.MessageIndex) { + s.cachedL1PriceData.mutex.Lock() + defer s.cachedL1PriceData.mutex.Unlock() + + if to < s.cachedL1PriceData.startOfL1PriceDataCache { + log.Info("trying to trim older cache which doesnt exist anymore") + } else if to >= s.cachedL1PriceData.endOfL1PriceDataCache { + s.cachedL1PriceData.startOfL1PriceDataCache = 0 + s.cachedL1PriceData.endOfL1PriceDataCache = 0 + s.cachedL1PriceData.msgToL1PriceData = []L1PriceDataOfMsg{} + } else { + newStart := to - s.cachedL1PriceData.startOfL1PriceDataCache + 1 + s.cachedL1PriceData.msgToL1PriceData = s.cachedL1PriceData.msgToL1PriceData[newStart:] + s.cachedL1PriceData.startOfL1PriceDataCache = to + 1 + } +} + +func (s *ExecutionEngine) Initialize(rustCacheSize uint32) { + if rustCacheSize != 0 { + programs.ResizeWasmLruCache(rustCacheSize) + } +} + func (s *ExecutionEngine) SetRecorder(recorder *BlockRecorder) { if s.Started() { panic("trying to set recorder after start") @@ -92,19 +185,23 @@ func (s *ExecutionEngine) EnablePrefetchBlock() { s.prefetchBlock = true } -func (s *ExecutionEngine) SetTransactionStreamer(streamer execution.TransactionStreamer) { +func (s *ExecutionEngine) SetConsensus(consensus execution.FullConsensusClient) { if s.Started() { - panic("trying to set transaction streamer after start") + panic("trying to set transaction consensus after start") } - if s.streamer != nil { - panic("trying to set transaction streamer when already set") + if s.consensus != nil { + panic("trying to set transaction consensus when already set") } - s.streamer = streamer + s.consensus = consensus +} + +func (s *ExecutionEngine) GetBatchFetcher() execution.BatchFetcher { + return s.consensus } -func (s *ExecutionEngine) Reorg(count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadata, oldMessages []*arbostypes.MessageWithMetadata) error { +func (s *ExecutionEngine) Reorg(count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadataAndBlockHash, oldMessages []*arbostypes.MessageWithMetadata) ([]*execution.MessageResult, error) { if count == 0 { - return errors.New("cannot reorg out genesis") + return nil, errors.New("cannot reorg out genesis") } s.createBlocksMutex.Lock() resequencing := false @@ -120,22 +217,29 @@ func (s *ExecutionEngine) Reorg(count arbutil.MessageIndex, newMessages []arbost targetBlock := s.bc.GetBlockByNumber(uint64(blockNum)) if targetBlock == nil { log.Warn("reorg target block not found", "block", blockNum) - return nil + return nil, nil } + tag := s.bc.StateCache().WasmCacheTag() + // reorg Rust-side VM state + C.stylus_reorg_vm(C.uint64_t(blockNum), C.uint32_t(tag)) + err := s.bc.ReorgToOldBlock(targetBlock) if err != nil { - return err + return nil, err } + + newMessagesResults := make([]*execution.MessageResult, 0, len(oldMessages)) for i := range newMessages { var msgForPrefetch *arbostypes.MessageWithMetadata if i < len(newMessages)-1 { - msgForPrefetch = &newMessages[i] + msgForPrefetch = &newMessages[i].MessageWithMeta } - err := s.digestMessageWithBlockMutex(count+arbutil.MessageIndex(i), &newMessages[i], msgForPrefetch) + msgResult, err := s.digestMessageWithBlockMutex(count+arbutil.MessageIndex(i), &newMessages[i].MessageWithMeta, msgForPrefetch) if err != nil { - return err + return nil, err } + newMessagesResults = append(newMessagesResults, msgResult) } if s.recorder != nil { s.recorder.ReorgTo(targetBlock.Header()) @@ -144,7 +248,7 @@ func (s *ExecutionEngine) Reorg(count arbutil.MessageIndex, newMessages []arbost s.resequenceChan <- oldMessages resequencing = true } - return nil + return newMessagesResults, nil } func (s *ExecutionEngine) getCurrentHeader() (*types.Header, error) { @@ -177,7 +281,7 @@ func (s *ExecutionEngine) NextDelayedMessageNumber() (uint64, error) { return currentHeader.Nonce.Uint64(), nil } -func messageFromTxes(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, txErrors []error) (*arbostypes.L1IncomingMessage, error) { +func MessageFromTxes(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, txErrors []error) (*arbostypes.L1IncomingMessage, error) { var l2Message []byte if len(txes) == 1 && txErrors[0] == nil { txBytes, err := txes[0].MarshalBinary() @@ -203,6 +307,9 @@ func messageFromTxes(header *arbostypes.L1IncomingMessageHeader, txes types.Tran l2Message = append(l2Message, txBytes...) } } + if len(l2Message) > arbostypes.MaxL2MessageSize { + return nil, errors.New("l2message too long") + } return &arbostypes.L1IncomingMessage{ Header: header, L2msg: l2Message, @@ -275,7 +382,7 @@ func (s *ExecutionEngine) sequencerWrapper(sequencerFunc func() (*types.Block, e } // We got SequencerInsertLockTaken // option 1: there was a race, we are no longer main sequencer - chosenErr := s.streamer.ExpectChosenSequencer() + chosenErr := s.consensus.ExpectChosenSequencer() if chosenErr != nil { return nil, chosenErr } @@ -300,6 +407,44 @@ func (s *ExecutionEngine) SequenceTransactions(header *arbostypes.L1IncomingMess }) } +// SequenceTransactionsWithProfiling runs SequenceTransactions with tracing and +// CPU profiling enabled. If the block creation takes longer than 2 seconds, it +// keeps both and prints out filenames in an error log line. +func (s *ExecutionEngine) SequenceTransactionsWithProfiling(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks) (*types.Block, error) { + pprofBuf, traceBuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil) + if err := pprof.StartCPUProfile(pprofBuf); err != nil { + log.Error("Starting CPU profiling", "error", err) + } + if err := trace.Start(traceBuf); err != nil { + log.Error("Starting tracing", "error", err) + } + start := time.Now() + res, err := s.SequenceTransactions(header, txes, hooks) + elapsed := time.Since(start) + pprof.StopCPUProfile() + trace.Stop() + if elapsed > 2*time.Second { + writeAndLog(pprofBuf, traceBuf) + return res, err + } + return res, err +} + +func writeAndLog(pprof, trace *bytes.Buffer) { + id := uuid.NewString() + pprofFile := path.Join(os.TempDir(), id+".pprof") + if err := os.WriteFile(pprofFile, pprof.Bytes(), 0o600); err != nil { + log.Error("Creating temporary file for pprof", "fileName", pprofFile, "error", err) + return + } + traceFile := path.Join(os.TempDir(), id+".trace") + if err := os.WriteFile(traceFile, trace.Bytes(), 0o600); err != nil { + log.Error("Creating temporary file for trace", "fileName", traceFile, "error", err) + return + } + log.Info("Transactions sequencing took longer than 2 seconds, created pprof and trace files", "pprof", pprofFile, "traceFile", traceFile) +} + func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks) (*types.Block, error) { lastBlockHeader, err := s.getCurrentHeader() if err != nil { @@ -323,6 +468,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. s.bc, s.bc.Config(), hooks, + false, ) if err != nil { return nil, err @@ -347,7 +493,12 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. return nil, nil } - msg, err := messageFromTxes(header, txes, hooks.TxErrors) + msg, err := MessageFromTxes(header, txes, hooks.TxErrors) + if err != nil { + return nil, err + } + + pos, err := s.BlockNumberToMessageIndex(lastBlockHeader.Number.Uint64() + 1) if err != nil { return nil, err } @@ -356,13 +507,12 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. Message: msg, DelayedMessagesRead: delayedMessagesRead, } - - pos, err := s.BlockNumberToMessageIndex(lastBlockHeader.Number.Uint64() + 1) + msgResult, err := s.resultFromHeader(block.Header()) if err != nil { return nil, err } - err = s.streamer.WriteMessageFromSequencer(pos, msgWithMeta) + err = s.consensus.WriteMessageFromSequencer(pos, msgWithMeta, *msgResult) if err != nil { return nil, err } @@ -373,6 +523,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. if err != nil { return nil, err } + s.cacheL1PriceDataOfMsg(pos, receipts, block, false) return block, nil } @@ -392,7 +543,7 @@ func (s *ExecutionEngine) sequenceDelayedMessageWithBlockMutex(message *arbostyp expectedDelayed := currentHeader.Nonce.Uint64() - lastMsg, err := s.BlockNumberToMessageIndex(currentHeader.Number.Uint64()) + pos, err := s.BlockNumberToMessageIndex(currentHeader.Number.Uint64() + 1) if err != nil { return nil, err } @@ -406,23 +557,30 @@ func (s *ExecutionEngine) sequenceDelayedMessageWithBlockMutex(message *arbostyp DelayedMessagesRead: delayedSeqNum + 1, } - err = s.streamer.WriteMessageFromSequencer(lastMsg+1, messageWithMeta) + startTime := time.Now() + block, statedb, receipts, err := s.createBlockFromNextMessage(&messageWithMeta, false) if err != nil { return nil, err } + blockCalcTime := time.Since(startTime) - startTime := time.Now() - block, statedb, receipts, err := s.createBlockFromNextMessage(&messageWithMeta) + msgResult, err := s.resultFromHeader(block.Header()) if err != nil { return nil, err } - err = s.appendBlock(block, statedb, receipts, time.Since(startTime)) + err = s.consensus.WriteMessageFromSequencer(pos, messageWithMeta, *msgResult) if err != nil { return nil, err } - log.Info("ExecutionEngine: Added DelayedMessages", "pos", lastMsg+1, "delayed", delayedSeqNum, "block-header", block.Header()) + err = s.appendBlock(block, statedb, receipts, blockCalcTime) + if err != nil { + return nil, err + } + s.cacheL1PriceDataOfMsg(pos, receipts, block, true) + + log.Info("ExecutionEngine: Added DelayedMessages", "pos", pos, "delayed", delayedSeqNum, "block-header", block.Header()) return block, nil } @@ -444,7 +602,7 @@ func (s *ExecutionEngine) MessageIndexToBlockNumber(messageNum arbutil.MessageIn } // must hold createBlockMutex -func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWithMetadata) (*types.Block, *state.StateDB, types.Receipts, error) { +func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWithMetadata, isMsgForPrefetch bool) (*types.Block, *state.StateDB, types.Receipts, error) { currentHeader := s.bc.CurrentBlock() if currentHeader == nil { return nil, nil, nil, errors.New("failed to get current block header") @@ -467,6 +625,11 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith statedb.StartPrefetcher("TransactionStreamer") defer statedb.StopPrefetcher() + batchFetcher := func(num uint64) ([]byte, error) { + data, _, err := s.consensus.FetchBatch(s.GetContext(), num) + return data, err + } + block, receipts, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, @@ -474,10 +637,8 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith statedb, s.bc, s.bc.Config(), - func(batchNum uint64) ([]byte, error) { - data, _, err := s.streamer.FetchBatch(batchNum) - return data, err - }, + batchFetcher, + isMsgForPrefetch, ) return block, statedb, receipts, err @@ -505,6 +666,8 @@ func (s *ExecutionEngine) appendBlock(block *types.Block, statedb *state.StateDB blockGasused += val } blockGasUsedHistogram.Update(int64(blockGasused)) + gasUsedSinceStartupCounter.Inc(int64(blockGasused)) + s.updateL1GasPriceEstimateMetric() return nil } @@ -523,64 +686,162 @@ func (s *ExecutionEngine) ResultAtPos(pos arbutil.MessageIndex) (*execution.Mess return s.resultFromHeader(s.bc.GetHeaderByNumber(s.MessageIndexToBlockNumber(pos))) } +func (s *ExecutionEngine) updateL1GasPriceEstimateMetric() { + bc := s.bc + latestHeader := bc.CurrentBlock() + latestState, err := bc.StateAt(latestHeader.Root) + if err != nil { + log.Error("error getting latest statedb while fetching l2 Estimate of L1 GasPrice") + return + } + arbState, err := arbosState.OpenSystemArbosState(latestState, nil, true) + if err != nil { + log.Error("error opening system arbos state while fetching l2 Estimate of L1 GasPrice") + return + } + l2EstimateL1GasPrice, err := arbState.L1PricingState().PricePerUnit() + if err != nil { + log.Error("error fetching l2 Estimate of L1 GasPrice") + return + } + l1GasPriceEstimateGauge.Update(l2EstimateL1GasPrice.Int64()) +} + +func (s *ExecutionEngine) getL1PricingSurplus() (int64, error) { + bc := s.bc + latestHeader := bc.CurrentBlock() + latestState, err := bc.StateAt(latestHeader.Root) + if err != nil { + return 0, errors.New("error getting latest statedb while fetching current L1 pricing surplus") + } + arbState, err := arbosState.OpenSystemArbosState(latestState, nil, true) + if err != nil { + return 0, errors.New("error opening system arbos state while fetching current L1 pricing surplus") + } + surplus, err := arbState.L1PricingState().GetL1PricingSurplus() + if err != nil { + return 0, errors.New("error fetching current L1 pricing surplus") + } + return surplus.Int64(), nil +} + +func (s *ExecutionEngine) cacheL1PriceDataOfMsg(seqNum arbutil.MessageIndex, receipts types.Receipts, block *types.Block, blockBuiltUsingDelayedMessage bool) { + var gasUsedForL1 uint64 + var callDataUnits uint64 + if !blockBuiltUsingDelayedMessage { + // s.cachedL1PriceData tracks L1 price data for messages posted by Nitro, + // so delayed messages should not update cummulative values kept on it. + + // First transaction in every block is an Arbitrum internal transaction, + // so we skip it here. + for i := 1; i < len(receipts); i++ { + gasUsedForL1 += receipts[i].GasUsedForL1 + } + for _, tx := range block.Transactions() { + callDataUnits += tx.CalldataUnits + } + } + l1GasCharged := gasUsedForL1 * block.BaseFee().Uint64() + + s.cachedL1PriceData.mutex.Lock() + defer s.cachedL1PriceData.mutex.Unlock() + + resetCache := func() { + s.cachedL1PriceData.startOfL1PriceDataCache = seqNum + s.cachedL1PriceData.endOfL1PriceDataCache = seqNum + s.cachedL1PriceData.msgToL1PriceData = []L1PriceDataOfMsg{{ + callDataUnits: callDataUnits, + cummulativeCallDataUnits: callDataUnits, + l1GasCharged: l1GasCharged, + cummulativeL1GasCharged: l1GasCharged, + }} + } + size := len(s.cachedL1PriceData.msgToL1PriceData) + if size == 0 || + s.cachedL1PriceData.startOfL1PriceDataCache == 0 || + s.cachedL1PriceData.endOfL1PriceDataCache == 0 || + arbutil.MessageIndex(size) != s.cachedL1PriceData.endOfL1PriceDataCache-s.cachedL1PriceData.startOfL1PriceDataCache+1 { + resetCache() + return + } + if seqNum != s.cachedL1PriceData.endOfL1PriceDataCache+1 { + if seqNum > s.cachedL1PriceData.endOfL1PriceDataCache+1 { + log.Info("message position higher then current end of l1 price data cache, resetting cache to this message") + resetCache() + } else if seqNum < s.cachedL1PriceData.startOfL1PriceDataCache { + log.Info("message position lower than start of l1 price data cache, ignoring") + } else { + log.Info("message position already seen in l1 price data cache, ignoring") + } + } else { + cummulativeCallDataUnits := s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeCallDataUnits + cummulativeL1GasCharged := s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeL1GasCharged + s.cachedL1PriceData.msgToL1PriceData = append(s.cachedL1PriceData.msgToL1PriceData, L1PriceDataOfMsg{ + callDataUnits: callDataUnits, + cummulativeCallDataUnits: cummulativeCallDataUnits + callDataUnits, + l1GasCharged: l1GasCharged, + cummulativeL1GasCharged: cummulativeL1GasCharged + l1GasCharged, + }) + s.cachedL1PriceData.endOfL1PriceDataCache = seqNum + } +} + // DigestMessage is used to create a block by executing msg against the latest state and storing it. // Also, while creating a block by executing msg against the latest state, // in parallel, creates a block by executing msgForPrefetch (msg+1) against the latest state // but does not store the block. // This helps in filling the cache, so that the next block creation is faster. -func (s *ExecutionEngine) DigestMessage(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) error { +func (s *ExecutionEngine) DigestMessage(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) (*execution.MessageResult, error) { if !s.createBlocksMutex.TryLock() { - return errors.New("createBlock mutex held") + return nil, errors.New("createBlock mutex held") } defer s.createBlocksMutex.Unlock() return s.digestMessageWithBlockMutex(num, msg, msgForPrefetch) } -func (s *ExecutionEngine) digestMessageWithBlockMutex(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) error { +func (s *ExecutionEngine) digestMessageWithBlockMutex(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) (*execution.MessageResult, error) { currentHeader, err := s.getCurrentHeader() if err != nil { - return err + return nil, err } curMsg, err := s.BlockNumberToMessageIndex(currentHeader.Number.Uint64()) if err != nil { - return err + return nil, err } if curMsg+1 != num { - return fmt.Errorf("wrong message number in digest got %d expected %d", num, curMsg+1) + return nil, fmt.Errorf("wrong message number in digest got %d expected %d", num, curMsg+1) } startTime := time.Now() - var wg sync.WaitGroup if s.prefetchBlock && msgForPrefetch != nil { - wg.Add(1) go func() { - defer wg.Done() - _, _, _, err := s.createBlockFromNextMessage(msgForPrefetch) + _, _, _, err := s.createBlockFromNextMessage(msgForPrefetch, true) if err != nil { return } }() } - block, statedb, receipts, err := s.createBlockFromNextMessage(msg) + block, statedb, receipts, err := s.createBlockFromNextMessage(msg, false) if err != nil { - return err + return nil, err } - wg.Wait() + err = s.appendBlock(block, statedb, receipts, time.Since(startTime)) if err != nil { - return err + return nil, err } + s.cacheL1PriceDataOfMsg(num, receipts, block, false) if time.Now().After(s.nextScheduledVersionCheck) { s.nextScheduledVersionCheck = time.Now().Add(time.Minute) arbState, err := arbosState.OpenSystemArbosState(statedb, nil, true) if err != nil { - return err + return nil, err } version, timestampInt, err := arbState.GetScheduledUpgrade() if err != nil { - return err + return nil, err } var timeUntilUpgrade time.Duration var timestamp time.Time @@ -616,7 +877,12 @@ func (s *ExecutionEngine) digestMessageWithBlockMutex(num arbutil.MessageIndex, case s.newBlockNotifier <- struct{}{}: default: } - return nil + + msgResult, err := s.resultFromHeader(block.Header()) + if err != nil { + return nil, err + } + return msgResult, nil } func (s *ExecutionEngine) ArbOSVersionForMessageNumber(messageNum arbutil.MessageIndex) (uint64, error) { diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 80c2939af..cb2bfe12e 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -51,6 +51,7 @@ type Config struct { TxLookupLimit uint64 `koanf:"tx-lookup-limit"` Dangerous DangerousConfig `koanf:"dangerous"` EnablePrefetchBlock bool `koanf:"enable-prefetch-block"` + SyncMonitor SyncMonitorConfig `koanf:"sync-monitor"` forwardingTarget string } @@ -83,6 +84,7 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { AddOptionsForNodeForwarderConfig(prefix+".forwarder", f) TxPreCheckerConfigAddOptions(prefix+".tx-pre-checker", f) CachingConfigAddOptions(prefix+".caching", f) + SyncMonitorConfigAddOptions(prefix+".sync-monitor", f) f.Uint64(prefix+".tx-lookup-limit", ConfigDefault.TxLookupLimit, "retain the ability to lookup transactions by hash for the past N blocks (0 = all blocks)") DangerousConfigAddOptions(prefix+".dangerous", f) f.Bool(prefix+".enable-prefetch-block", ConfigDefault.EnablePrefetchBlock, "enable prefetching of blocks") @@ -105,6 +107,7 @@ var ConfigDefault = Config{ func ConfigDefaultNonSequencerTest() *Config { config := ConfigDefault + config.Caching = TestCachingConfig config.ParentChainReader = headerreader.TestConfig config.Sequencer.Enable = false config.Forwarder = DefaultTestForwarderConfig @@ -117,9 +120,10 @@ func ConfigDefaultNonSequencerTest() *Config { func ConfigDefaultTest() *Config { config := ConfigDefault + config.Caching = TestCachingConfig config.Sequencer = TestSequencerConfig - config.ForwardingTarget = "null" config.ParentChainReader = headerreader.TestConfig + config.ForwardingTarget = "null" _ = config.Validate() @@ -138,7 +142,9 @@ type ExecutionNode struct { Sequencer *Sequencer // either nil or same as TxPublisher TxPublisher TransactionPublisher ConfigFetcher ConfigFetcher + SyncMonitor *SyncMonitor ParentChainReader *headerreader.HeaderReader + ClassicOutbox *ClassicOutboxRetriever started atomic.Bool } @@ -169,6 +175,8 @@ func CreateExecutionNode( if err != nil { return nil, err } + } else if config.Sequencer.Enable { + log.Warn("sequencer enabled without l1 client") } if config.Sequencer.Enable { @@ -192,7 +200,7 @@ func CreateExecutionNode( txprecheckConfigFetcher := func() *TxPreCheckerConfig { return &configFetcher().TxPreChecker } txPublisher = NewTxPreChecker(txPublisher, l2BlockChain, txprecheckConfigFetcher) - arbInterface, err := NewArbInterface(execEngine, txPublisher) + arbInterface, err := NewArbInterface(l2BlockChain, txPublisher) if err != nil { return nil, err } @@ -205,6 +213,20 @@ func CreateExecutionNode( return nil, err } + syncMon := NewSyncMonitor(&config.SyncMonitor, execEngine) + + var classicOutbox *ClassicOutboxRetriever + + if l2BlockChain.Config().ArbitrumChainParams.GenesisBlockNum > 0 { + classicMsgDb, err := stack.OpenDatabase("classic-msg", 0, 0, "classicmsg/", true) // TODO can we skip using ExtraOptions here? + if err != nil { + log.Warn("Classic Msg Database not found", "err", err) + classicOutbox = nil + } else { + classicOutbox = NewClassicOutboxRetriever(classicMsgDb) + } + } + apis := []rpc.API{{ Namespace: "arb", Version: "1.0", @@ -248,13 +270,20 @@ func CreateExecutionNode( Sequencer: sequencer, TxPublisher: txPublisher, ConfigFetcher: configFetcher, + SyncMonitor: syncMon, ParentChainReader: parentChainReader, + ClassicOutbox: classicOutbox, }, nil } -func (n *ExecutionNode) Initialize(ctx context.Context, arbnode interface{}, sync arbitrum.SyncProgressBackend) error { - n.ArbInterface.Initialize(arbnode) +func (n *ExecutionNode) MarkFeedStart(to arbutil.MessageIndex) { + n.ExecEngine.MarkFeedStart(to) +} + +func (n *ExecutionNode) Initialize(ctx context.Context) error { + n.ExecEngine.Initialize(n.ConfigFetcher().Caching.StylusLRUCache) + n.ArbInterface.Initialize(n) err := n.Backend.Start() if err != nil { return fmt.Errorf("error starting geth backend: %w", err) @@ -263,7 +292,7 @@ func (n *ExecutionNode) Initialize(ctx context.Context, arbnode interface{}, syn if err != nil { return fmt.Errorf("error initializing transaction publisher: %w", err) } - err = n.Backend.APIBackend().SetSyncBackend(sync) + err = n.Backend.APIBackend().SetSyncBackend(n.SyncMonitor) if err != nil { return fmt.Errorf("error setting sync backend: %w", err) } @@ -317,10 +346,10 @@ func (n *ExecutionNode) StopAndWait() { // } } -func (n *ExecutionNode) DigestMessage(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) error { +func (n *ExecutionNode) DigestMessage(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) (*execution.MessageResult, error) { return n.ExecEngine.DigestMessage(num, msg, msgForPrefetch) } -func (n *ExecutionNode) Reorg(count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadata, oldMessages []*arbostypes.MessageWithMetadata) error { +func (n *ExecutionNode) Reorg(count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadataAndBlockHash, oldMessages []*arbostypes.MessageWithMetadata) ([]*execution.MessageResult, error) { return n.ExecEngine.Reorg(count, newMessages, oldMessages) } func (n *ExecutionNode) HeadMessageNumber() (arbutil.MessageIndex, error) { @@ -361,11 +390,13 @@ func (n *ExecutionNode) Pause() { n.Sequencer.Pause() } } + func (n *ExecutionNode) Activate() { if n.Sequencer != nil { n.Sequencer.Activate() } } + func (n *ExecutionNode) ForwardTo(url string) error { if n.Sequencer != nil { return n.Sequencer.ForwardTo(url) @@ -373,9 +404,12 @@ func (n *ExecutionNode) ForwardTo(url string) error { return errors.New("forwardTo not supported - sequencer not active") } } -func (n *ExecutionNode) SetTransactionStreamer(streamer execution.TransactionStreamer) { - n.ExecEngine.SetTransactionStreamer(streamer) + +func (n *ExecutionNode) SetConsensusClient(consensus execution.FullConsensusClient) { + n.ExecEngine.SetConsensus(consensus) + n.SyncMonitor.SetConsensusInfo(consensus) } + func (n *ExecutionNode) MessageIndexToBlockNumber(messageNum arbutil.MessageIndex) uint64 { return n.ExecEngine.MessageIndexToBlockNumber(messageNum) } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 5db38cbb4..2bace9b67 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -10,6 +10,7 @@ import ( "math" "math/big" "runtime/debug" + "strconv" "strings" "sync" "sync/atomic" @@ -25,10 +26,12 @@ import ( "github.com/ethereum/go-ethereum/arbitrum" "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" @@ -51,21 +54,31 @@ var ( successfulBlocksCounter = metrics.NewRegisteredCounter("arb/sequencer/block/successful", nil) conditionalTxRejectedBySequencerCounter = metrics.NewRegisteredCounter("arb/sequencer/condtionaltx/rejected", nil) conditionalTxAcceptedBySequencerCounter = metrics.NewRegisteredCounter("arb/sequencer/condtionaltx/accepted", nil) + l1GasPriceGauge = metrics.NewRegisteredGauge("arb/sequencer/l1gasprice", nil) + callDataUnitsBacklogGauge = metrics.NewRegisteredGauge("arb/sequencer/calldataunitsbacklog", nil) + unusedL1GasChargeGauge = metrics.NewRegisteredGauge("arb/sequencer/unusedl1gascharge", nil) + currentSurplusGauge = metrics.NewRegisteredGauge("arb/sequencer/currentsurplus", nil) + expectedSurplusGauge = metrics.NewRegisteredGauge("arb/sequencer/expectedsurplus", nil) ) type SequencerConfig struct { - Enable bool `koanf:"enable"` - MaxBlockSpeed time.Duration `koanf:"max-block-speed" reload:"hot"` - MaxRevertGasReject uint64 `koanf:"max-revert-gas-reject" reload:"hot"` - MaxAcceptableTimestampDelta time.Duration `koanf:"max-acceptable-timestamp-delta" reload:"hot"` - SenderWhitelist string `koanf:"sender-whitelist"` - Forwarder ForwarderConfig `koanf:"forwarder"` - QueueSize int `koanf:"queue-size"` - QueueTimeout time.Duration `koanf:"queue-timeout" reload:"hot"` - NonceCacheSize int `koanf:"nonce-cache-size" reload:"hot"` - MaxTxDataSize int `koanf:"max-tx-data-size" reload:"hot"` - NonceFailureCacheSize int `koanf:"nonce-failure-cache-size" reload:"hot"` - NonceFailureCacheExpiry time.Duration `koanf:"nonce-failure-cache-expiry" reload:"hot"` + Enable bool `koanf:"enable"` + MaxBlockSpeed time.Duration `koanf:"max-block-speed" reload:"hot"` + MaxRevertGasReject uint64 `koanf:"max-revert-gas-reject" reload:"hot"` + MaxAcceptableTimestampDelta time.Duration `koanf:"max-acceptable-timestamp-delta" reload:"hot"` + SenderWhitelist string `koanf:"sender-whitelist"` + Forwarder ForwarderConfig `koanf:"forwarder"` + QueueSize int `koanf:"queue-size"` + QueueTimeout time.Duration `koanf:"queue-timeout" reload:"hot"` + NonceCacheSize int `koanf:"nonce-cache-size" reload:"hot"` + MaxTxDataSize int `koanf:"max-tx-data-size" reload:"hot"` + NonceFailureCacheSize int `koanf:"nonce-failure-cache-size" reload:"hot"` + NonceFailureCacheExpiry time.Duration `koanf:"nonce-failure-cache-expiry" reload:"hot"` + ExpectedSurplusSoftThreshold string `koanf:"expected-surplus-soft-threshold" reload:"hot"` + ExpectedSurplusHardThreshold string `koanf:"expected-surplus-hard-threshold" reload:"hot"` + EnableProfiling bool `koanf:"enable-profiling" reload:"hot"` + expectedSurplusSoftThreshold int + expectedSurplusHardThreshold int } func (c *SequencerConfig) Validate() error { @@ -78,6 +91,23 @@ func (c *SequencerConfig) Validate() error { return fmt.Errorf("sequencer sender whitelist entry \"%v\" is not a valid address", address) } } + var err error + if c.ExpectedSurplusSoftThreshold != "default" { + if c.expectedSurplusSoftThreshold, err = strconv.Atoi(c.ExpectedSurplusSoftThreshold); err != nil { + return fmt.Errorf("invalid expected-surplus-soft-threshold value provided in batchposter config %w", err) + } + } + if c.ExpectedSurplusHardThreshold != "default" { + if c.expectedSurplusHardThreshold, err = strconv.Atoi(c.ExpectedSurplusHardThreshold); err != nil { + return fmt.Errorf("invalid expected-surplus-hard-threshold value provided in batchposter config %w", err) + } + } + if c.expectedSurplusSoftThreshold < c.expectedSurplusHardThreshold { + return errors.New("expected-surplus-soft-threshold cannot be lower than expected-surplus-hard-threshold") + } + if c.MaxTxDataSize > arbostypes.MaxL2MessageSize-50000 { + return errors.New("max-tx-data-size too large for MaxL2MessageSize") + } return nil } @@ -86,7 +116,7 @@ type SequencerConfigFetcher func() *SequencerConfig var DefaultSequencerConfig = SequencerConfig{ Enable: false, MaxBlockSpeed: time.Millisecond * 250, - MaxRevertGasReject: params.TxGas + 10000, + MaxRevertGasReject: 0, MaxAcceptableTimestampDelta: time.Hour, Forwarder: DefaultSequencerForwarderConfig, QueueSize: 1024, @@ -94,24 +124,30 @@ var DefaultSequencerConfig = SequencerConfig{ NonceCacheSize: 1024, // 95% of the default batch poster limit, leaving 5KB for headers and such // This default is overridden for L3 chains in applyChainParameters in cmd/nitro/nitro.go - MaxTxDataSize: 95000, - NonceFailureCacheSize: 1024, - NonceFailureCacheExpiry: time.Second, + MaxTxDataSize: 95000, + NonceFailureCacheSize: 1024, + NonceFailureCacheExpiry: time.Second, + ExpectedSurplusSoftThreshold: "default", + ExpectedSurplusHardThreshold: "default", + EnableProfiling: false, } var TestSequencerConfig = SequencerConfig{ - Enable: true, - MaxBlockSpeed: time.Millisecond * 10, - MaxRevertGasReject: params.TxGas + 10000, - MaxAcceptableTimestampDelta: time.Hour, - SenderWhitelist: "", - Forwarder: DefaultTestForwarderConfig, - QueueSize: 128, - QueueTimeout: time.Second * 5, - NonceCacheSize: 4, - MaxTxDataSize: 95000, - NonceFailureCacheSize: 1024, - NonceFailureCacheExpiry: time.Second, + Enable: true, + MaxBlockSpeed: time.Millisecond * 10, + MaxRevertGasReject: params.TxGas + 10000, + MaxAcceptableTimestampDelta: time.Hour, + SenderWhitelist: "", + Forwarder: DefaultTestForwarderConfig, + QueueSize: 128, + QueueTimeout: time.Second * 5, + NonceCacheSize: 4, + MaxTxDataSize: 95000, + NonceFailureCacheSize: 1024, + NonceFailureCacheExpiry: time.Second, + ExpectedSurplusSoftThreshold: "default", + ExpectedSurplusHardThreshold: "default", + EnableProfiling: false, } func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -127,23 +163,26 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Int(prefix+".max-tx-data-size", DefaultSequencerConfig.MaxTxDataSize, "maximum transaction size the sequencer will accept") f.Int(prefix+".nonce-failure-cache-size", DefaultSequencerConfig.NonceFailureCacheSize, "number of transactions with too high of a nonce to keep in memory while waiting for their predecessor") f.Duration(prefix+".nonce-failure-cache-expiry", DefaultSequencerConfig.NonceFailureCacheExpiry, "maximum amount of time to wait for a predecessor before rejecting a tx with nonce too high") + f.String(prefix+".expected-surplus-soft-threshold", DefaultSequencerConfig.ExpectedSurplusSoftThreshold, "if expected surplus is lower than this value, warnings are posted") + f.String(prefix+".expected-surplus-hard-threshold", DefaultSequencerConfig.ExpectedSurplusHardThreshold, "if expected surplus is lower than this value, new incoming transactions will be denied") + f.Bool(prefix+".enable-profiling", DefaultSequencerConfig.EnableProfiling, "enable CPU profiling and tracing") } type txQueueItem struct { tx *types.Transaction + txSize int // size in bytes of the marshalled transaction options *arbitrum_types.ConditionalOptions resultChan chan<- error - returnedResult bool + returnedResult *atomic.Bool ctx context.Context firstAppearance time.Time } func (i *txQueueItem) returnResult(err error) { - if i.returnedResult { + if i.returnedResult.Swap(true) { log.Error("attempting to return result to already finished queue item", "err", err) return } - i.returnedResult = true i.resultChan <- err close(i.resultChan) } @@ -291,6 +330,10 @@ type Sequencer struct { activeMutex sync.Mutex pauseChan chan struct{} forwarder *TxForwarder + + expectedSurplusMutex sync.RWMutex + expectedSurplus int64 + expectedSurplusUpdated bool } func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderReader, configFetcher SequencerConfigFetcher) (*Sequencer, error) { @@ -364,6 +407,17 @@ func ctxWithTimeout(ctx context.Context, timeout time.Duration) (context.Context } func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { + config := s.config() + // Only try to acquire Rlock and check for hard threshold if l1reader is not nil + // And hard threshold was enabled, this prevents spamming of read locks when not needed + if s.l1Reader != nil && config.ExpectedSurplusHardThreshold != "default" { + s.expectedSurplusMutex.RLock() + if s.expectedSurplusUpdated && s.expectedSurplus < int64(config.expectedSurplusHardThreshold) { + return errors.New("currently not accepting transactions due to expected surplus being below threshold") + } + s.expectedSurplusMutex.RUnlock() + } + sequencerBacklogGauge.Inc(1) defer sequencerBacklogGauge.Dec(1) @@ -392,7 +446,12 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran return types.ErrTxTypeNotSupported } - queueTimeout := s.config().QueueTimeout + txBytes, err := tx.MarshalBinary() + if err != nil { + return err + } + + queueTimeout := config.QueueTimeout queueCtx, cancelFunc := ctxWithTimeout(parentCtx, queueTimeout) defer cancelFunc() @@ -403,9 +462,10 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran resultChan := make(chan error, 1) queueItem := txQueueItem{ tx, + len(txBytes), options, resultChan, - false, + &atomic.Bool{}, queueCtx, time.Now(), } @@ -481,7 +541,7 @@ func (s *Sequencer) CheckHealth(ctx context.Context) error { if pauseChan != nil { return nil } - return s.execEngine.streamer.ExpectChosenSequencer() + return s.execEngine.consensus.ExpectChosenSequencer() } func (s *Sequencer) ForwardTarget() string { @@ -628,7 +688,8 @@ func (s *Sequencer) expireNonceFailures() *time.Timer { } // There's no guarantee that returned tx nonces will be correct -func (s *Sequencer) precheckNonces(queueItems []txQueueItem) []txQueueItem { +func (s *Sequencer) precheckNonces(queueItems []txQueueItem, totalBlockSize int) []txQueueItem { + config := s.config() bc := s.execEngine.bc latestHeader := bc.CurrentBlock() latestState, err := bc.StateAt(latestHeader.Root) @@ -678,7 +739,13 @@ func (s *Sequencer) precheckNonces(queueItems []txQueueItem) []txQueueItem { if err != nil { revivingFailure.queueItem.returnResult(err) } else { - nextQueueItem = &revivingFailure.queueItem + if arbmath.SaturatingAdd(totalBlockSize, revivingFailure.queueItem.txSize) > config.MaxTxDataSize { + // This tx would be too large to add to this block + s.txRetryQueue.Push(revivingFailure.queueItem) + } else { + nextQueueItem = &revivingFailure.queueItem + totalBlockSize += revivingFailure.queueItem.txSize + } } } } else if txNonce < stateNonce || txNonce > pendingNonce { @@ -714,7 +781,7 @@ func (s *Sequencer) precheckNonces(queueItems []txQueueItem) []txQueueItem { func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { var queueItems []txQueueItem - var totalBatchSize int + var totalBlockSize int defer func() { panicErr := recover() @@ -722,7 +789,8 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { log.Error("sequencer block creation panicked", "panic", panicErr, "backtrace", string(debug.Stack())) // Return an internal error to any queue items we were trying to process for _, item := range queueItems { - if !item.returnedResult { + // This can race, but that's alright, worst case is a log line in returnResult + if !item.returnedResult.Load() { item.returnResult(sequencerInternalError) } } @@ -785,37 +853,47 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { queueItem.returnResult(err) continue } - txBytes, err := queueItem.tx.MarshalBinary() - if err != nil { - queueItem.returnResult(err) - continue - } - if len(txBytes) > config.MaxTxDataSize { + if queueItem.txSize > config.MaxTxDataSize { // This tx is too large queueItem.returnResult(txpool.ErrOversizedData) continue } - if totalBatchSize+len(txBytes) > config.MaxTxDataSize { + if totalBlockSize+queueItem.txSize > config.MaxTxDataSize { // This tx would be too large to add to this batch s.txRetryQueue.Push(queueItem) // End the batch here to put this tx in the next one break } - totalBatchSize += len(txBytes) + totalBlockSize += queueItem.txSize queueItems = append(queueItems, queueItem) } s.nonceCache.Resize(config.NonceCacheSize) // Would probably be better in a config hook but this is basically free s.nonceCache.BeginNewBlock() - queueItems = s.precheckNonces(queueItems) + queueItems = s.precheckNonces(queueItems, totalBlockSize) txes := make([]*types.Transaction, len(queueItems)) hooks := s.makeSequencingHooks() hooks.ConditionalOptionsForTx = make([]*arbitrum_types.ConditionalOptions, len(queueItems)) + totalBlockSize = 0 // recompute the totalBlockSize to double check it for i, queueItem := range queueItems { txes[i] = queueItem.tx + totalBlockSize = arbmath.SaturatingAdd(totalBlockSize, queueItem.txSize) hooks.ConditionalOptionsForTx[i] = queueItem.options } + if totalBlockSize > config.MaxTxDataSize { + for _, queueItem := range queueItems { + s.txRetryQueue.Push(queueItem) + } + log.Error( + "put too many transactions in a block", + "numTxes", len(queueItems), + "totalBlockSize", totalBlockSize, + "maxTxDataSize", config.MaxTxDataSize, + ) + return false + } + if s.handleInactive(ctx, queueItems) { return false } @@ -827,13 +905,16 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { s.L1BlockAndTimeMutex.Unlock() if s.l1Reader != nil && (l1Block == 0 || math.Abs(float64(l1Timestamp)-float64(timestamp)) > config.MaxAcceptableTimestampDelta.Seconds()) { + for _, queueItem := range queueItems { + s.txRetryQueue.Push(queueItem) + } log.Error( "cannot sequence: unknown L1 block or L1 timestamp too far from local clock time", "l1Block", l1Block, "l1Timestamp", time.Unix(int64(l1Timestamp), 0), "localTimestamp", time.Unix(int64(timestamp), 0), ) - return false + return true } header := &arbostypes.L1IncomingMessageHeader{ @@ -846,7 +927,15 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { } start := time.Now() - block, err := s.execEngine.SequenceTransactions(header, txes, hooks) + var ( + block *types.Block + err error + ) + if config.EnableProfiling { + block, err = s.execEngine.SequenceTransactionsWithProfiling(header, txes, hooks) + } else { + block, err = s.execEngine.SequenceTransactions(header, txes, hooks) + } elapsed := time.Since(start) blockCreationTimer.Update(elapsed) if elapsed >= time.Second*5 { @@ -944,14 +1033,84 @@ func (s *Sequencer) Initialize(ctx context.Context) error { return nil } +var ( + usableBytesInBlob = big.NewInt(int64(len(kzg4844.Blob{}) * 31 / 32)) + blobTxBlobGasPerBlob = big.NewInt(params.BlobTxBlobGasPerBlob) +) + +func (s *Sequencer) updateExpectedSurplus(ctx context.Context) (int64, error) { + header, err := s.l1Reader.LastHeader(ctx) + if err != nil { + return 0, fmt.Errorf("error encountered getting latest header from l1reader while updating expectedSurplus: %w", err) + } + l1GasPrice := header.BaseFee.Uint64() + if header.BlobGasUsed != nil { + if header.ExcessBlobGas != nil { + blobFeePerByte := eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*header.ExcessBlobGas, *header.BlobGasUsed)) + blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) + blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) + if l1GasPrice > blobFeePerByte.Uint64()/16 { + l1GasPrice = blobFeePerByte.Uint64() / 16 + } + } + } + surplus, err := s.execEngine.getL1PricingSurplus() + if err != nil { + return 0, fmt.Errorf("error encountered getting l1 pricing surplus while updating expectedSurplus: %w", err) + } + backlogL1GasCharged := int64(s.execEngine.backlogL1GasCharged()) + backlogCallDataUnits := int64(s.execEngine.backlogCallDataUnits()) + expectedSurplus := int64(surplus) + backlogL1GasCharged - backlogCallDataUnits*int64(l1GasPrice) + // update metrics + l1GasPriceGauge.Update(int64(l1GasPrice)) + callDataUnitsBacklogGauge.Update(backlogCallDataUnits) + unusedL1GasChargeGauge.Update(backlogL1GasCharged) + currentSurplusGauge.Update(surplus) + expectedSurplusGauge.Update(expectedSurplus) + config := s.config() + if config.ExpectedSurplusSoftThreshold != "default" && expectedSurplus < int64(config.expectedSurplusSoftThreshold) { + log.Warn("expected surplus is below soft threshold", "value", expectedSurplus, "threshold", config.expectedSurplusSoftThreshold) + } + return expectedSurplus, nil +} + func (s *Sequencer) Start(ctxIn context.Context) error { s.StopWaiter.Start(ctxIn, s) + config := s.config() + if (config.ExpectedSurplusHardThreshold != "default" || config.ExpectedSurplusSoftThreshold != "default") && s.l1Reader == nil { + return errors.New("expected surplus soft/hard thresholds are enabled but l1Reader is nil") + } + if s.l1Reader != nil { initialBlockNr := atomic.LoadUint64(&s.l1BlockNumber) if initialBlockNr == 0 { return errors.New("sequencer not initialized") } + expectedSurplus, err := s.updateExpectedSurplus(ctxIn) + if err != nil { + if config.ExpectedSurplusHardThreshold != "default" { + return fmt.Errorf("expected-surplus-hard-threshold is enabled but error fetching initial expected surplus value: %w", err) + } + log.Error("expected-surplus-soft-threshold is enabled but error fetching initial expected surplus value", "err", err) + } else { + s.expectedSurplus = expectedSurplus + s.expectedSurplusUpdated = true + } + s.CallIteratively(func(ctx context.Context) time.Duration { + expectedSurplus, err := s.updateExpectedSurplus(ctxIn) + s.expectedSurplusMutex.Lock() + defer s.expectedSurplusMutex.Unlock() + if err != nil { + s.expectedSurplusUpdated = false + log.Error("expected surplus soft/hard thresholds are enabled but unable to fetch latest expected surplus, retrying", "err", err) + return 0 + } + s.expectedSurplusUpdated = true + s.expectedSurplus = expectedSurplus + return 5 * time.Second + }) + headerChan, cancel := s.l1Reader.Subscribe(false) s.LaunchThread(func(ctx context.Context) { @@ -973,8 +1132,7 @@ func (s *Sequencer) Start(ctxIn context.Context) error { s.CallIteratively(func(ctx context.Context) time.Duration { nextBlock := time.Now().Add(s.config().MaxBlockSpeed) - madeBlock := s.createBlock(ctx) - if madeBlock { + if s.createBlock(ctx) { // Note: this may return a negative duration, but timers are fine with that (they treat negative durations as 0). return time.Until(nextBlock) } diff --git a/execution/gethexec/sync_monitor.go b/execution/gethexec/sync_monitor.go new file mode 100644 index 000000000..564c6d74b --- /dev/null +++ b/execution/gethexec/sync_monitor.go @@ -0,0 +1,120 @@ +package gethexec + +import ( + "context" + + "github.com/offchainlabs/nitro/execution" + "github.com/pkg/errors" + flag "github.com/spf13/pflag" +) + +type SyncMonitorConfig struct { + SafeBlockWaitForBlockValidator bool `koanf:"safe-block-wait-for-block-validator"` + FinalizedBlockWaitForBlockValidator bool `koanf:"finalized-block-wait-for-block-validator"` +} + +var DefaultSyncMonitorConfig = SyncMonitorConfig{ + SafeBlockWaitForBlockValidator: false, + FinalizedBlockWaitForBlockValidator: false, +} + +func SyncMonitorConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Bool(prefix+".safe-block-wait-for-block-validator", DefaultSyncMonitorConfig.SafeBlockWaitForBlockValidator, "wait for block validator to complete before returning safe block number") + f.Bool(prefix+".finalized-block-wait-for-block-validator", DefaultSyncMonitorConfig.FinalizedBlockWaitForBlockValidator, "wait for block validator to complete before returning finalized block number") +} + +type SyncMonitor struct { + config *SyncMonitorConfig + consensus execution.ConsensusInfo + exec *ExecutionEngine +} + +func NewSyncMonitor(config *SyncMonitorConfig, exec *ExecutionEngine) *SyncMonitor { + return &SyncMonitor{ + config: config, + exec: exec, + } +} + +func (s *SyncMonitor) FullSyncProgressMap() map[string]interface{} { + res := s.consensus.FullSyncProgressMap() + + res["consensusSyncTarget"] = s.consensus.SyncTargetMessageCount() + + header, err := s.exec.getCurrentHeader() + if err != nil { + res["currentHeaderError"] = err + } else { + blockNum := header.Number.Uint64() + res["blockNum"] = blockNum + messageNum, err := s.exec.BlockNumberToMessageIndex(blockNum) + if err != nil { + res["messageOfLastBlockError"] = err + } else { + res["messageOfLastBlock"] = messageNum + } + } + + return res +} + +func (s *SyncMonitor) SyncProgressMap() map[string]interface{} { + if s.consensus.Synced() { + built, err := s.exec.HeadMessageNumber() + consensusSyncTarget := s.consensus.SyncTargetMessageCount() + if err == nil && built+1 >= consensusSyncTarget { + return make(map[string]interface{}) + } + } + return s.FullSyncProgressMap() +} + +func (s *SyncMonitor) SafeBlockNumber(ctx context.Context) (uint64, error) { + if s.consensus == nil { + return 0, errors.New("not set up for safeblock") + } + msg, err := s.consensus.GetSafeMsgCount(ctx) + if err != nil { + return 0, err + } + if s.config.SafeBlockWaitForBlockValidator { + latestValidatedCount, err := s.consensus.ValidatedMessageCount() + if err != nil { + return 0, err + } + if msg > latestValidatedCount { + msg = latestValidatedCount + } + } + block := s.exec.MessageIndexToBlockNumber(msg - 1) + return block, nil +} + +func (s *SyncMonitor) FinalizedBlockNumber(ctx context.Context) (uint64, error) { + if s.consensus == nil { + return 0, errors.New("not set up for safeblock") + } + msg, err := s.consensus.GetFinalizedMsgCount(ctx) + if err != nil { + return 0, err + } + if s.config.FinalizedBlockWaitForBlockValidator { + latestValidatedCount, err := s.consensus.ValidatedMessageCount() + if err != nil { + return 0, err + } + if msg > latestValidatedCount { + msg = latestValidatedCount + } + } + block := s.exec.MessageIndexToBlockNumber(msg - 1) + return block, nil +} + +func (s *SyncMonitor) Synced() bool { + return len(s.SyncProgressMap()) == 0 +} + +func (s *SyncMonitor) SetConsensusInfo(consensus execution.ConsensusInfo) { + s.consensus = consensus +} diff --git a/execution/gethexec/tx_pre_checker.go b/execution/gethexec/tx_pre_checker.go index cff8b04d3..1a48d75fd 100644 --- a/execution/gethexec/tx_pre_checker.go +++ b/execution/gethexec/tx_pre_checker.go @@ -187,7 +187,7 @@ func PreCheckTx(bc *core.BlockChain, chainConfig *params.ChainConfig, header *ty } balance := statedb.GetBalance(sender) cost := tx.Cost() - if arbmath.BigLessThan(balance, cost) { + if arbmath.BigLessThan(balance.ToBig(), cost) { return fmt.Errorf("%w: address %v have %v want %v", core.ErrInsufficientFunds, sender, balance, cost) } if config.Strictness >= TxPreCheckerStrictnessFullValidation && tx.Nonce() > stateNonce { diff --git a/execution/gethexec/wasmstorerebuilder.go b/execution/gethexec/wasmstorerebuilder.go new file mode 100644 index 000000000..dcbee45a3 --- /dev/null +++ b/execution/gethexec/wasmstorerebuilder.go @@ -0,0 +1,115 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package gethexec + +import ( + "bytes" + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/arbitrum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/offchainlabs/nitro/arbos/arbosState" +) + +var RebuildingPositionKey []byte = []byte("_rebuildingPosition") // contains the codehash upto which rebuilding of wasm store was last completed. Initialized to common.Hash{} at the start +var RebuildingStartBlockHashKey []byte = []byte("_rebuildingStartBlockHash") // contains the block hash of starting block when rebuilding of wasm store first began +var RebuildingDone common.Hash = common.BytesToHash([]byte("_done")) // indicates that the rebuilding is done, if RebuildingPositionKey holds this value it implies rebuilding was completed + +func ReadFromKeyValueStore[T any](store ethdb.KeyValueStore, key []byte) (T, error) { + var empty T + posBytes, err := store.Get(key) + if err != nil { + return empty, err + } + var val T + err = rlp.DecodeBytes(posBytes, &val) + if err != nil { + return empty, fmt.Errorf("error decoding value stored for key in the KeyValueStore: %w", err) + } + return val, nil +} + +func WriteToKeyValueStore[T any](store ethdb.KeyValueStore, key []byte, val T) error { + valBytes, err := rlp.EncodeToBytes(val) + if err != nil { + return err + } + err = store.Put(key, valBytes) + if err != nil { + return err + } + return nil +} + +// RebuildWasmStore function runs a loop looking at every codehash in diskDb, checking if its an activated stylus contract and +// saving it to wasm store if it doesnt already exists. When errored it logs them and silently returns +// +// It stores the status of rebuilding to wasm store by updating the codehash (of the latest sucessfully checked contract) in +// RebuildingPositionKey after every second of work. +// +// It also stores a special value that is only set once when rebuilding commenced in RebuildingStartBlockHashKey as the block +// time of the latest block when rebuilding was first called, this is used to avoid recomputing of assembly and module of +// contracts that were created after rebuilding commenced since they would anyway already be added during sync. +func RebuildWasmStore(ctx context.Context, wasmStore ethdb.KeyValueStore, chainDb ethdb.Database, maxRecreateStateDepth int64, l2Blockchain *core.BlockChain, position, rebuildingStartBlockHash common.Hash) error { + var err error + var stateDb *state.StateDB + latestHeader := l2Blockchain.CurrentBlock() + // Attempt to get state at the start block when rebuilding commenced, if not available (in case of non-archival nodes) use latest state + rebuildingStartHeader := l2Blockchain.GetHeaderByHash(rebuildingStartBlockHash) + stateDb, _, err = arbitrum.StateAndHeaderFromHeader(ctx, chainDb, l2Blockchain, maxRecreateStateDepth, rebuildingStartHeader, nil) + if err != nil { + log.Info("Error getting state at start block of rebuilding wasm store, attempting rebuilding with latest state", "err", err) + stateDb, _, err = arbitrum.StateAndHeaderFromHeader(ctx, chainDb, l2Blockchain, maxRecreateStateDepth, latestHeader, nil) + if err != nil { + return fmt.Errorf("error getting state at latest block, aborting rebuilding: %w", err) + } + } + diskDb := stateDb.Database().DiskDB() + arbState, err := arbosState.OpenSystemArbosState(stateDb, nil, true) + if err != nil { + return fmt.Errorf("error getting arbos state, aborting rebuilding: %w", err) + } + programs := arbState.Programs() + iter := diskDb.NewIterator(rawdb.CodePrefix, position[:]) + defer iter.Release() + lastStatusUpdate := time.Now() + for iter.Next() { + codeHashBytes := bytes.TrimPrefix(iter.Key(), rawdb.CodePrefix) + codeHash := common.BytesToHash(codeHashBytes) + code := iter.Value() + if state.IsStylusProgram(code) { + if err := programs.SaveActiveProgramToWasmStore(stateDb, codeHash, code, latestHeader.Time, l2Blockchain.Config().DebugMode(), rebuildingStartHeader.Time); err != nil { + return fmt.Errorf("error while rebuilding of wasm store, aborting rebuilding: %w", err) + } + } + // After every one second of work, update the rebuilding position + // This also notifies user that we are working on rebuilding + if time.Since(lastStatusUpdate) >= time.Second || ctx.Err() != nil { + log.Info("Storing rebuilding status to disk", "codeHash", codeHash) + if err := WriteToKeyValueStore(wasmStore, RebuildingPositionKey, codeHash); err != nil { + return fmt.Errorf("error updating codehash position in rebuilding of wasm store: %w", err) + } + // If outer context is cancelled we should terminate rebuilding + // We attempted to write the latest checked codeHash to wasm store + if ctx.Err() != nil { + return ctx.Err() + } + lastStatusUpdate = time.Now() + } + } + // Set rebuilding position to done indicating completion + if err := WriteToKeyValueStore(wasmStore, RebuildingPositionKey, RebuildingDone); err != nil { + return fmt.Errorf("error updating codehash position in rebuilding of wasm store to done: %w", err) + } + log.Info("Rebuilding of wasm store was successful") + return nil +} diff --git a/execution/interface.go b/execution/interface.go index 2cbbf550a..32ec7dd0f 100644 --- a/execution/interface.go +++ b/execution/interface.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/validator" @@ -21,6 +22,7 @@ type RecordResult struct { BlockHash common.Hash Preimages map[common.Hash][]byte BatchInfo []validator.BatchInfo + UserWasms state.UserWasms } var ErrRetrySequencer = errors.New("please retry transaction") @@ -28,8 +30,8 @@ var ErrSequencerInsertLockTaken = errors.New("insert lock taken") // always needed type ExecutionClient interface { - DigestMessage(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) error - Reorg(count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadata, oldMessages []*arbostypes.MessageWithMetadata) error + DigestMessage(num arbutil.MessageIndex, msg *arbostypes.MessageWithMetadata, msgForPrefetch *arbostypes.MessageWithMetadata) (*MessageResult, error) + Reorg(count arbutil.MessageIndex, newMessages []arbostypes.MessageWithMetadataAndBlockHash, oldMessages []*arbostypes.MessageWithMetadata) ([]*MessageResult, error) HeadMessageNumber() (arbutil.MessageIndex, error) HeadMessageNumberSync(t *testing.T) (arbutil.MessageIndex, error) ResultAtPos(pos arbutil.MessageIndex) (*MessageResult, error) @@ -54,7 +56,7 @@ type ExecutionSequencer interface { ForwardTo(url string) error SequenceDelayedMessage(message *arbostypes.L1IncomingMessage, delayedSeqNum uint64) error NextDelayedMessageNumber() (uint64, error) - SetTransactionStreamer(streamer TransactionStreamer) + MarkFeedStart(to arbutil.MessageIndex) } type FullExecutionClient interface { @@ -67,19 +69,35 @@ type FullExecutionClient interface { Maintenance() error - // TODO: only used to get safe/finalized block numbers - MessageIndexToBlockNumber(messageNum arbutil.MessageIndex) uint64 - ArbOSVersionForMessageNumber(messageNum arbutil.MessageIndex) (uint64, error) } // not implemented in execution, used as input +// BatchFetcher is required for any execution node type BatchFetcher interface { - FetchBatch(batchNum uint64) ([]byte, common.Hash, error) + FetchBatch(ctx context.Context, batchNum uint64) ([]byte, common.Hash, error) + FindInboxBatchContainingMessage(message arbutil.MessageIndex) (uint64, bool, error) + GetBatchParentChainBlock(seqNum uint64) (uint64, error) } -type TransactionStreamer interface { - BatchFetcher - WriteMessageFromSequencer(pos arbutil.MessageIndex, msgWithMeta arbostypes.MessageWithMetadata) error +type ConsensusInfo interface { + Synced() bool + FullSyncProgressMap() map[string]interface{} + SyncTargetMessageCount() arbutil.MessageIndex + + // TODO: switch from pulling to pushing safe/finalized + GetSafeMsgCount(ctx context.Context) (arbutil.MessageIndex, error) + GetFinalizedMsgCount(ctx context.Context) (arbutil.MessageIndex, error) + ValidatedMessageCount() (arbutil.MessageIndex, error) +} + +type ConsensusSequencer interface { + WriteMessageFromSequencer(pos arbutil.MessageIndex, msgWithMeta arbostypes.MessageWithMetadata, msgResult MessageResult) error ExpectChosenSequencer() error } + +type FullConsensusClient interface { + BatchFetcher + ConsensusInfo + ConsensusSequencer +} diff --git a/nodeInterface/NodeInterface.go b/execution/nodeInterface/NodeInterface.go similarity index 88% rename from nodeInterface/NodeInterface.go rename to execution/nodeInterface/NodeInterface.go index bdcfb569f..9179a5271 100644 --- a/nodeInterface/NodeInterface.go +++ b/execution/nodeInterface/NodeInterface.go @@ -20,14 +20,12 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/retryables" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" - "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/merkletree" ) @@ -53,90 +51,129 @@ var merkleTopic common.Hash var l2ToL1TxTopic common.Hash var l2ToL1TransactionTopic common.Hash -var blockInGenesis = errors.New("") -var blockAfterLatestBatch = errors.New("") - func (n NodeInterface) NitroGenesisBlock(c ctx) (huge, error) { block := n.backend.ChainConfig().ArbitrumChainParams.GenesisBlockNum return arbmath.UintToBig(block), nil } +// bool will be false but no error if behind genesis +func (n NodeInterface) blockNumToMessageIndex(blockNum uint64) (arbutil.MessageIndex, bool, error) { + node, err := gethExecFromNodeInterfaceBackend(n.backend) + if err != nil { + return 0, false, err + } + blockchain, err := blockchainFromNodeInterfaceBackend(n.backend) + if err != nil { + return 0, false, err + } + if blockNum < blockchain.Config().ArbitrumChainParams.GenesisBlockNum { + return 0, true, nil + } + msgIndex, err := node.ExecEngine.BlockNumberToMessageIndex(blockNum) + if err != nil { + return 0, false, err + } + return msgIndex, true, nil +} + +func (n NodeInterface) msgNumToInboxBatch(msgIndex arbutil.MessageIndex) (uint64, bool, error) { + node, err := gethExecFromNodeInterfaceBackend(n.backend) + if err != nil { + return 0, false, err + } + fetcher := node.ExecEngine.GetBatchFetcher() + if fetcher == nil { + return 0, false, errors.New("batch fetcher not set") + } + return fetcher.FindInboxBatchContainingMessage(msgIndex) +} + func (n NodeInterface) FindBatchContainingBlock(c ctx, evm mech, blockNum uint64) (uint64, error) { - node, err := arbNodeFromNodeInterfaceBackend(n.backend) + msgIndex, found, err := n.blockNumToMessageIndex(blockNum) if err != nil { return 0, err } - return findBatchContainingBlock(node, node.TxStreamer.GenesisBlockNumber(), blockNum) + if !found { + return 0, fmt.Errorf("block %v is part of genesis", blockNum) + } + res, found, err := n.msgNumToInboxBatch(msgIndex) + if err == nil && !found { + return 0, errors.New("block not yet found on any batch") + } + return res, err } func (n NodeInterface) GetL1Confirmations(c ctx, evm mech, blockHash bytes32) (uint64, error) { - node, err := arbNodeFromNodeInterfaceBackend(n.backend) + node, err := gethExecFromNodeInterfaceBackend(n.backend) if err != nil { return 0, err } - if node.InboxReader == nil { - return 0, nil - } - bc, err := blockchainFromNodeInterfaceBackend(n.backend) + blockchain, err := blockchainFromNodeInterfaceBackend(n.backend) if err != nil { return 0, err } - header := bc.GetHeaderByHash(blockHash) + header := blockchain.GetHeaderByHash(blockHash) if header == nil { return 0, errors.New("unknown block hash") } blockNum := header.Number.Uint64() - genesis := node.TxStreamer.GenesisBlockNumber() - batch, err := findBatchContainingBlock(node, genesis, blockNum) + + // blocks behind genesis are treated as belonging to batch 0 + msgNum, _, err := n.blockNumToMessageIndex(blockNum) if err != nil { - if errors.Is(err, blockInGenesis) { - batch = 0 - } else if errors.Is(err, blockAfterLatestBatch) { - return 0, nil - } else { - return 0, err - } + return 0, err + } + // batches not yet posted have 0 confirmations but no error + batchNum, found, err := n.msgNumToInboxBatch(msgNum) + if err != nil { + return 0, err + } + if !found { + return 0, nil } - meta, err := node.InboxTracker.GetBatchMetadata(batch) + parentChainBlockNum, err := node.ExecEngine.GetBatchFetcher().GetBatchParentChainBlock(batchNum) if err != nil { return 0, err } - if node.L1Reader.IsParentChainArbitrum() { - parentChainClient := node.L1Reader.Client() + + if node.ParentChainReader.IsParentChainArbitrum() { + parentChainClient := node.ParentChainReader.Client() parentNodeInterface, err := node_interfacegen.NewNodeInterface(types.NodeInterfaceAddress, parentChainClient) if err != nil { return 0, err } - parentChainBlock, err := parentChainClient.BlockByNumber(n.context, new(big.Int).SetUint64(meta.ParentChainBlock)) + parentChainBlock, err := parentChainClient.BlockByNumber(n.context, new(big.Int).SetUint64(parentChainBlockNum)) if err != nil { // Hide the parent chain RPC error from the client in case it contains sensitive information. // Likely though, this error is just "not found" because the block got reorg'd. - return 0, fmt.Errorf("failed to get parent chain block %v containing batch", meta.ParentChainBlock) + return 0, fmt.Errorf("failed to get parent chain block %v containing batch", parentChainBlockNum) } confs, err := parentNodeInterface.GetL1Confirmations(&bind.CallOpts{Context: n.context}, parentChainBlock.Hash()) if err != nil { log.Warn( "Failed to get L1 confirmations from parent chain", - "blockNumber", meta.ParentChainBlock, + "blockNumber", parentChainBlockNum, "blockHash", parentChainBlock.Hash(), "err", err, ) return 0, fmt.Errorf("failed to get L1 confirmations from parent chain for block %v", parentChainBlock.Hash()) } return confs, nil } - latestL1Block, latestBatchCount := node.InboxReader.GetLastReadBlockAndBatchCount() - if latestBatchCount <= batch { - return 0, nil // batch was reorg'd out? - } - if latestL1Block < meta.ParentChainBlock || arbutil.BlockNumberToMessageCount(blockNum, genesis) > meta.MessageCount { + if node.ParentChainReader == nil { return 0, nil } - canonicalHash := bc.GetCanonicalHash(header.Number.Uint64()) - if canonicalHash != header.Hash() { - return 0, errors.New("block hash is non-canonical") + latestHeader, err := node.ParentChainReader.LastHeaderWithError() + if err != nil { + return 0, err + } + if latestHeader == nil { + return 0, errors.New("no headers read from l1") } - confs := (latestL1Block - meta.ParentChainBlock) + 1 + node.InboxReader.GetDelayBlocks() - return confs, nil + latestBlockNum := latestHeader.Number.Uint64() + if latestBlockNum < parentChainBlockNum { + return 0, nil + } + return (latestBlockNum - parentChainBlockNum), nil } func (n NodeInterface) EstimateRetryableTicket( @@ -176,12 +213,11 @@ func (n NodeInterface) EstimateRetryableTicket( } // ArbitrumSubmitRetryableTx is unsigned so the following won't panic - msg, err := core.TransactionToMessage(types.NewTx(submitTx), types.NewArbitrumSigner(nil), nil) + msg, err := core.TransactionToMessage(types.NewTx(submitTx), types.NewArbitrumSigner(nil), nil, core.MessageGasEstimationMode) if err != nil { return err } - msg.TxRunMode = core.MessageGasEstimationMode *n.returnMessage.message = *msg *n.returnMessage.changed = true return nil @@ -561,42 +597,18 @@ func (n NodeInterface) GasEstimateComponents( return total, gasForL1, baseFee, l1BaseFeeEstimate, nil } -func findBatchContainingBlock(node *arbnode.Node, genesis uint64, block uint64) (uint64, error) { - if block <= genesis { - return 0, fmt.Errorf("%wblock %v is part of genesis", blockInGenesis, block) - } - pos := arbutil.BlockNumberToMessageCount(block, genesis) - 1 - high, err := node.InboxTracker.GetBatchCount() - if err != nil { - return 0, err - } - high-- - latestCount, err := node.InboxTracker.GetBatchMessageCount(high) - if err != nil { - return 0, err - } - latestBlock := arbutil.MessageCountToBlockNumber(latestCount, genesis) - if int64(block) > latestBlock { - return 0, fmt.Errorf( - "%wrequested block %v is after latest on-chain block %v published in batch %v", - blockAfterLatestBatch, block, latestBlock, high, - ) - } - return staker.FindBatchContainingMessageIndex(node.InboxTracker, pos, high) -} - func (n NodeInterface) LegacyLookupMessageBatchProof(c ctx, evm mech, batchNum huge, index uint64) ( proof []bytes32, path huge, l2Sender addr, l1Dest addr, l2Block huge, l1Block huge, timestamp huge, amount huge, calldataForL1 []byte, err error) { - node, err := arbNodeFromNodeInterfaceBackend(n.backend) + node, err := gethExecFromNodeInterfaceBackend(n.backend) if err != nil { return } - if node.ClassicOutboxRetriever == nil { + if node.ClassicOutbox == nil { err = errors.New("this node doesnt support classicLookupMessageBatchProof") return } - msg, err := node.ClassicOutboxRetriever.GetMsg(batchNum, index) + msg, err := node.ClassicOutbox.GetMsg(batchNum, index) if err != nil { return } diff --git a/nodeInterface/NodeInterfaceDebug.go b/execution/nodeInterface/NodeInterfaceDebug.go similarity index 100% rename from nodeInterface/NodeInterfaceDebug.go rename to execution/nodeInterface/NodeInterfaceDebug.go diff --git a/nodeInterface/virtual-contracts.go b/execution/nodeInterface/virtual-contracts.go similarity index 94% rename from nodeInterface/virtual-contracts.go rename to execution/nodeInterface/virtual-contracts.go index b35381a77..d72ad0da8 100644 --- a/nodeInterface/virtual-contracts.go +++ b/execution/nodeInterface/virtual-contracts.go @@ -15,10 +15,10 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/gethhook" "github.com/offchainlabs/nitro/precompiles" "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" @@ -88,7 +88,7 @@ func init() { return msg, nil, nil } - evm, vmError := backend.GetEVM(ctx, msg, statedb, header, &vm.Config{NoBaseFee: true}, blockCtx) + evm := backend.GetEVM(ctx, msg, statedb, header, &vm.Config{NoBaseFee: true}, blockCtx) go func() { <-ctx.Done() evm.Cancel() @@ -110,7 +110,7 @@ func init() { ReturnData: output, ScheduledTxes: nil, } - return msg, res, vmError() + return msg, res, statedb.Error() } return msg, nil, nil } @@ -173,16 +173,16 @@ func init() { merkleTopic = arbSys.Events["SendMerkleUpdate"].ID } -func arbNodeFromNodeInterfaceBackend(backend BackendAPI) (*arbnode.Node, error) { +func gethExecFromNodeInterfaceBackend(backend BackendAPI) (*gethexec.ExecutionNode, error) { apiBackend, ok := backend.(*arbitrum.APIBackend) if !ok { return nil, errors.New("API backend isn't Arbitrum") } - arbNode, ok := apiBackend.GetArbitrumNode().(*arbnode.Node) + exec, ok := apiBackend.GetArbitrumNode().(*gethexec.ExecutionNode) if !ok { return nil, errors.New("failed to get Arbitrum Node from backend") } - return arbNode, nil + return exec, nil } func blockchainFromNodeInterfaceBackend(backend BackendAPI) (*core.BlockChain, error) { diff --git a/fastcache b/fastcache index 8053d350d..f9d9f1105 160000 --- a/fastcache +++ b/fastcache @@ -1 +1 @@ -Subproject commit 8053d350d785b5dd877e208e1f0205bbd36faee7 +Subproject commit f9d9f11052817d478af08b64d139d5f09ec3a68f diff --git a/gethhook/geth-hook.go b/gethhook/geth-hook.go index dcd178871..776e8cc45 100644 --- a/gethhook/geth-hook.go +++ b/gethhook/geth-hook.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/precompiles" ) @@ -55,16 +56,33 @@ func init() { vm.PrecompiledContractsArbitrum[k] = v } + for k, v := range vm.PrecompiledContractsCancun { + vm.PrecompiledAddressesArbOS30 = append(vm.PrecompiledAddressesArbOS30, k) + vm.PrecompiledContractsArbOS30[k] = v + } + precompileErrors := make(map[[4]byte]abi.Error) for addr, precompile := range precompiles.Precompiles() { for _, errABI := range precompile.Precompile().GetErrorABIs() { - var id [4]byte - copy(id[:], errABI.ID[:4]) - precompileErrors[id] = errABI + precompileErrors[[4]byte(errABI.ID.Bytes())] = errABI } var wrapped vm.AdvancedPrecompile = ArbosPrecompileWrapper{precompile} - vm.PrecompiledContractsArbitrum[addr] = wrapped - vm.PrecompiledAddressesArbitrum = append(vm.PrecompiledAddressesArbitrum, addr) + vm.PrecompiledContractsArbOS30[addr] = wrapped + vm.PrecompiledAddressesArbOS30 = append(vm.PrecompiledAddressesArbOS30, addr) + + if precompile.Precompile().ArbosVersion() < params.ArbosVersion_Stylus { + vm.PrecompiledContractsArbitrum[addr] = wrapped + vm.PrecompiledAddressesArbitrum = append(vm.PrecompiledAddressesArbitrum, addr) + } + } + + for addr, precompile := range vm.PrecompiledContractsArbitrum { + vm.PrecompiledContractsArbOS30[addr] = precompile + vm.PrecompiledAddressesArbOS30 = append(vm.PrecompiledAddressesArbOS30, addr) + } + for addr, precompile := range vm.PrecompiledContractsP256Verify { + vm.PrecompiledContractsArbOS30[addr] = precompile + vm.PrecompiledAddressesArbOS30 = append(vm.PrecompiledAddressesArbOS30, addr) } core.RenderRPCError = func(data []byte) error { diff --git a/gethhook/geth_test.go b/gethhook/geth_test.go index 6274a5411..99bfa4ae1 100644 --- a/gethhook/geth_test.go +++ b/gethhook/geth_test.go @@ -110,7 +110,7 @@ func TestEthDepositMessage(t *testing.T) { RunMessagesThroughAPI(t, [][]byte{serialized, serialized2}, statedb) - balanceAfter := statedb.GetBalance(addr) + balanceAfter := statedb.GetBalance(addr).ToBig() if balanceAfter.Cmp(new(big.Int).Add(balance.Big(), balance2.Big())) != 0 { Fail(t) } diff --git a/go-ethereum b/go-ethereum deleted file mode 160000 index a8c6813c8..000000000 --- a/go-ethereum +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a8c6813c85488a23d2c527b1e20e398323d349d0 diff --git a/go.mod b/go.mod index 649f147ac..50162ec74 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/offchainlabs/nitro -go 1.20 +go 1.21 replace github.com/VictoriaMetrics/fastcache => ./fastcache @@ -15,13 +15,13 @@ require ( github.com/Layr-Labs/eigenda v0.6.1 github.com/Layr-Labs/eigenda/api v0.6.1 github.com/Shopify/toxiproxy v2.1.4+incompatible - github.com/alicebob/miniredis/v2 v2.21.0 + github.com/alicebob/miniredis/v2 v2.32.1 github.com/andybalholm/brotli v1.0.4 - github.com/aws/aws-sdk-go-v2 v1.26.0 - github.com/aws/aws-sdk-go-v2/config v1.27.9 - github.com/aws/aws-sdk-go-v2/credentials v1.17.9 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.13 - github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 + github.com/aws/aws-sdk-go-v2 v1.21.2 + github.com/aws/aws-sdk-go-v2/config v1.18.45 + github.com/aws/aws-sdk-go-v2/credentials v1.13.43 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10 + github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 github.com/cavaliergopher/grab/v3 v3.0.1 github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 github.com/codeclysm/extract/v3 v3.0.2 @@ -29,7 +29,11 @@ require ( github.com/enescakir/emoji v1.0.0 github.com/ethereum/go-ethereum v1.13.14 github.com/fatih/structtag v1.2.0 - github.com/gdamore/tcell/v2 v2.6.0 + github.com/gdamore/tcell/v2 v2.7.1 + github.com/go-redis/redis/v8 v8.11.5 + github.com/gobwas/httphead v0.1.0 + github.com/gobwas/ws v1.2.1 + github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 github.com/google/go-cmp v0.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/holiman/uint256 v1.2.4 @@ -37,309 +41,138 @@ require ( github.com/ipfs/go-libipfs v0.6.2 github.com/ipfs/interface-go-ipfs-core v0.11.0 github.com/ipfs/kubo v0.19.1 + github.com/google/uuid v1.3.0 github.com/knadh/koanf v1.4.0 - github.com/libp2p/go-libp2p v0.27.8 - github.com/multiformats/go-multiaddr v0.12.1 - github.com/multiformats/go-multihash v0.2.3 + github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f + github.com/mitchellh/mapstructure v1.4.1 + github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v3 v3.0.1 - github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703 + github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 github.com/spf13/pflag v1.0.5 github.com/wealdtech/go-merkletree v1.0.1-0.20230205101955-ec7a95ea11ca - golang.org/x/crypto v0.18.0 - golang.org/x/sys v0.16.0 - golang.org/x/term v0.16.0 - golang.org/x/tools v0.15.0 + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/wasmerio/wasmer-go v1.0.4 + github.com/wealdtech/go-merkletree v1.0.0 + golang.org/x/crypto v0.21.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sys v0.18.0 + golang.org/x/term v0.18.0 + golang.org/x/tools v0.16.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) -require github.com/gofrs/flock v0.8.1 // indirect - require ( - bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc // indirect - github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect - github.com/DataDog/zstd v1.5.2 // indirect + github.com/DataDog/zstd v1.4.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect - github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect - github.com/aws/smithy-go v1.20.1 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect + github.com/aws/smithy-go v1.15.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect - github.com/cenkalti/backoff v2.2.1+incompatible // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/ceramicnetwork/go-dag-jose v0.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect + github.com/cockroachdb/redact v1.1.3 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect - github.com/containerd/cgroups v1.1.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect - github.com/cskr/pubsub v1.0.2 // indirect - github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/dgraph-io/badger v1.6.2 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.7.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/elastic/gosigar v0.14.2 // indirect - github.com/emirpasic/gods v1.18.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 // indirect - github.com/flynn/noise v1.0.0 // indirect - github.com/francoispqt/gojay v1.2.13 // indirect + github.com/fjl/memsize v0.0.2 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gammazero/deque v0.2.1 // indirect + github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect github.com/gdamore/encoding v1.0.0 // indirect - github.com/getsentry/sentry-go v0.18.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-logr/stdr v1.2.2 // indirect + github.com/getsentry/sentry-go v0.12.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect - github.com/go-stack/stack v1.8.1 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.1.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/flatbuffers v23.5.26+incompatible // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect - github.com/gorilla/mux v1.8.0 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/h2non/filetype v1.0.6 // indirect - github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect + github.com/hashicorp/go-bexpr v0.1.10 // indirect + github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect - github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/go-bitfield v1.1.0 // indirect - github.com/ipfs/go-block-format v0.1.1 // indirect - github.com/ipfs/go-blockservice v0.5.1 // indirect - github.com/ipfs/go-cidutil v0.1.0 // indirect - github.com/ipfs/go-datastore v0.6.0 // indirect - github.com/ipfs/go-delegated-routing v0.7.0 // indirect - github.com/ipfs/go-ds-badger v0.3.0 // indirect - github.com/ipfs/go-ds-flatfs v0.5.1 // indirect - github.com/ipfs/go-ds-leveldb v0.5.0 // indirect - github.com/ipfs/go-ds-measure v0.2.0 // indirect - github.com/ipfs/go-fetcher v1.6.1 // indirect - github.com/ipfs/go-filestore v1.2.0 // indirect - github.com/ipfs/go-fs-lock v0.0.7 // indirect - github.com/ipfs/go-graphsync v0.14.1 // indirect - github.com/ipfs/go-ipfs-blockstore v1.2.0 // indirect - github.com/ipfs/go-ipfs-chunker v0.0.5 // indirect - github.com/ipfs/go-ipfs-delay v0.0.1 // indirect - github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect - github.com/ipfs/go-ipfs-exchange-interface v0.2.0 // indirect - github.com/ipfs/go-ipfs-exchange-offline v0.3.0 // indirect - github.com/ipfs/go-ipfs-keystore v0.1.0 // indirect - github.com/ipfs/go-ipfs-pinner v0.3.0 // indirect - github.com/ipfs/go-ipfs-posinfo v0.0.1 // indirect - github.com/ipfs/go-ipfs-pq v0.0.3 // indirect - github.com/ipfs/go-ipfs-provider v0.8.1 // indirect - github.com/ipfs/go-ipfs-routing v0.3.0 // indirect - github.com/ipfs/go-ipfs-util v0.0.2 // indirect - github.com/ipfs/go-ipld-cbor v0.0.6 // indirect - github.com/ipfs/go-ipld-format v0.4.0 // indirect - github.com/ipfs/go-ipld-git v0.1.1 // indirect - github.com/ipfs/go-ipld-legacy v0.1.1 // indirect - github.com/ipfs/go-ipns v0.3.0 // indirect - github.com/ipfs/go-log v1.0.5 // indirect - github.com/ipfs/go-log/v2 v2.5.1 // indirect - github.com/ipfs/go-merkledag v0.9.0 // indirect - github.com/ipfs/go-metrics-interface v0.0.1 // indirect - github.com/ipfs/go-mfs v0.2.1 // indirect - github.com/ipfs/go-namesys v0.7.0 // indirect - github.com/ipfs/go-path v0.3.1 // indirect - github.com/ipfs/go-peertaskqueue v0.8.1 // indirect - github.com/ipfs/go-unixfs v0.4.4 // indirect - github.com/ipfs/go-unixfsnode v1.5.2 // indirect - github.com/ipfs/go-verifcid v0.0.2 // indirect - github.com/ipld/edelweiss v0.2.0 // indirect - github.com/ipld/go-codec-dagpb v1.5.0 // indirect - github.com/ipld/go-ipld-prime v0.19.0 // indirect - github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect - github.com/jbenet/goprocess v0.1.4 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect - github.com/klauspost/compress v1.16.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/koron/go-ssdp v0.0.4 // indirect + github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-cidranger v1.1.0 // indirect - github.com/libp2p/go-doh-resolver v0.4.0 // indirect - github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect - github.com/libp2p/go-libp2p-kad-dht v0.21.1 // indirect - github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect - github.com/libp2p/go-libp2p-pubsub v0.9.0 // indirect - github.com/libp2p/go-libp2p-pubsub-router v0.6.0 // indirect - github.com/libp2p/go-libp2p-record v0.2.0 // indirect - github.com/libp2p/go-libp2p-routing-helpers v0.6.2 // indirect - github.com/libp2p/go-libp2p-xor v0.1.0 // indirect - github.com/libp2p/go-mplex v0.7.0 // indirect - github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-nat v0.1.0 // indirect - github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/libp2p/go-reuseport v0.2.0 // indirect - github.com/libp2p/go-yamux/v4 v4.0.0 // indirect - github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/miekg/dns v1.1.53 // indirect - github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect - github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect - github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect - github.com/multiformats/go-base32 v0.1.0 // indirect - github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect - github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.8.1 // indirect - github.com/multiformats/go-multistream v0.4.1 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.11.0 // indirect - github.com/opencontainers/runtime-spec v1.0.2 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/openzipkin/zipkin-go v0.4.0 // indirect - github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.3 // indirect - github.com/quic-go/qtls-go1-20 v0.2.3 // indirect - github.com/quic-go/quic-go v0.33.0 // indirect - github.com/quic-go/webtransport-go v0.5.2 // indirect - github.com/raulk/go-watchdog v1.3.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/rhnvrm/simples3 v0.6.1 // indirect - github.com/rivo/uniseg v0.4.3 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/rs/cors v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/samber/lo v1.36.0 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/status-im/keycard-go v0.2.0 // indirect github.com/supranational/blst v0.3.11 // indirect - github.com/urfave/cli/v2 v2.27.1 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tyler-smith/go-bip39 v1.1.0 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect - github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect - github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect - github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect - github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/jaeger v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.7.0 // indirect - go.opentelemetry.io/otel/exporters/zipkin v1.7.0 // indirect - go.opentelemetry.io/otel/sdk v1.7.0 // indirect - go.opentelemetry.io/otel/trace v1.7.0 // indirect - go.opentelemetry.io/proto/otlp v0.16.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.16.1 // indirect - go.uber.org/fx v1.19.2 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - go4.org v0.0.0-20200411211856-f5505b9728dd // indirect - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.20.0 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect + go.opencensus.io v0.22.5 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/square/go-jose.v2 v2.5.1 // indirect - lukechampine.com/blake3 v1.1.7 // indirect - nhooyr.io/websocket v1.8.7 // indirect + google.golang.org/protobuf v1.30.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) - -require ( - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/gobwas/httphead v0.1.0 - github.com/gobwas/pool v0.2.1 // indirect - github.com/gobwas/ws v1.1.0 - github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 - github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f -) - -require ( - github.com/StackExchange/wmi v1.2.1 // indirect - github.com/VictoriaMetrics/fastcache v1.12.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fjl/memsize v0.0.2 // indirect - github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-redis/redis/v8 v8.11.4 - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/go-bexpr v0.1.10 // indirect - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect - github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/mitchellh/mapstructure v1.4.2 - github.com/mitchellh/pointerstructure v1.2.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/rs/cors v1.7.0 // indirect - github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/status-im/keycard-go v0.2.0 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - github.com/tyler-smith/go-bip39 v1.1.0 // indirect -) diff --git a/go.sum b/go.sum index 05f581be0..290beae5f 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc h1:utDghgcjE8u+EBjHOgYT+dJPcnDF05KqWMBcjuJy510= -bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -34,131 +30,105 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= -dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= -dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= -github.com/Layr-Labs/eigenda v0.6.1 h1:uU04t+dsR5oHsbr+A5XIeJdyZIfNW3YvG03dMTKLSK4= -github.com/Layr-Labs/eigenda v0.6.1/go.mod h1:XongI0xM6ks66DzxvTpF2yi4x2QH0X2RgEbKl/WFebY= -github.com/Layr-Labs/eigenda/api v0.6.1 h1:TAstOttTmFZQoFlZtgu/rNktNOhx62TwRFMxGOhUx8M= -github.com/Layr-Labs/eigenda/api v0.6.1/go.mod h1:kVXqWM13s/1hXyv9QdHweWAbKin9MeOBbS4i8c9rLbU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5ljuFxkLGPNem5Ui+KBjFJzKg4Fv2fnxe4dvzpM= -github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.21.0 h1:CdmwIlKUWFBDS+4464GtQiQ0R1vpzOgu4Vnd74rBL7M= -github.com/alicebob/miniredis/v2 v2.21.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88= +github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo= +github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/arduino/go-paths-helper v1.2.0 h1:qDW93PR5IZUN/jzO4rCtexiwF8P4OIcOmcSgAYLZfY4= github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= -github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo= +github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= +github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= -github.com/aws/aws-sdk-go-v2/config v1.27.9/go.mod h1:dK1FQfpwpql83kbD873E9vz4FyAxuJtR22wzoXn3qq0= +github.com/aws/aws-sdk-go-v2/config v1.15.5/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= +github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes= +github.com/aws/aws-sdk-go-v2/config v1.18.45/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE= github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao= -github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0/go.mod h1:9YWk7VW+eyKsoIL6/CljkTrNVWBSK9pkqOPUuijid4A= +github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8= +github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75lpnOHSBkPUZxZfGkrI3HI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.13 h1:F+PUZee9mlfpEJVZdgyewRumKekS9O3fftj8fEMt0rQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.13/go.mod h1:Rl7i2dEWGHGsBIJCpUxlRt7VwK/HyXxICxdvIRssQHE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10 h1:JL7cY85hyjlgfA29MMyAlItX+JYIH9XsxgMBS7jtlqA= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10/go.mod h1:p+ul5bLZSDRRXCZ/vePvfmZBH9akozXBJA5oMshWa5U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4 h1:SIkD6T4zGQ+1YIit22wi37CGNkrE7mXV1vNA5VpI3TI= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.4/go.mod h1:XfeqbsG0HNedNs0GT+ju4Bs+pFAwsrlzcRdMvdNVf5s= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 h1:C21IDZCm9Yu5xqjb3fKmxDoYvJXtw1DNlOmLZEIlY1M= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1/go.mod h1:l/BbcfqDCT3hePawhy4ZRtewjtdkl6GWtd9/U+1penQ= github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.6 h1:NkHCgg0Ck86c5PTOzBZ0JRccI51suJDg5lgFtxBu1ek= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.6/go.mod h1:mjTpxjC8v4SeINTngrnKFgm2QUi+Jm+etTbCxh8W4uU= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4 h1:uDj2K47EM1reAYU9jVlQ1M5YENI1u6a/TxJpf6AeOLA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4/go.mod h1:XKCODf4RKHppc96c2EZBGV/oCUC7OClxAo2MEyg4pIk= -github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 h1:r3o2YsgW9zRcIP3Q0WCmttFVhTuugeKIvT5z9xDspc0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0/go.mod h1:w2E4f8PUfNtyjfL6Iu+mWI96FGttE03z3UdNcUEC4tA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 h1:RE/DlZLYrz1OOmq8F28IXHLksuuvlpzUbvJ+SESCZBI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4/go.mod h1:oudbsSdDtazNj47z1ut1n37re9hDsKpk2ZI3v7KSxq0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 h1:LCQKnopq2t4oQS3VKivlYTzAHCTJZZoQICM9fny7KHY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9/go.mod h1:iMYipLPXlWpBJ0KFX7QJHZ84rBydHBY8as2aQICTPWk= github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4/go.mod h1:cPDwJwsP4Kff9mldCXAmddjJL6JGQqtA3Mzer2zyr88= +github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k= +github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg= github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsViglqlG9eEFYxNryTZS5rn3QE= +github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU= +github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= -github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= +github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8= +github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -166,76 +136,35 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= -github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/ceramicnetwork/go-dag-jose v0.1.0 h1:yJ/HVlfKpnD3LdYP03AHyTvbm3BpPiz2oZiOeReJRdU= -github.com/ceramicnetwork/go-dag-jose v0.1.0/go.mod h1:qYA1nYt0X8u4XoMAVoOV3upUVKtrxy/I670Dg5F0wjI= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f h1:6jduT9Hfc0njg5jJ1DdKCFPdMBrp/mdZfCpa5h+WM74= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= @@ -243,7 +172,6 @@ github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwP github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codeclysm/extract/v3 v3.0.2 h1:sB4LcE3Php7LkhZwN0n2p8GCwZe92PEQutdbGURf5xc= github.com/codeclysm/extract/v3 v3.0.2/go.mod h1:NKsw+hqua9H+Rlwy/w/3Qgt9jDonYEgB6wJu+25eOKw= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= @@ -251,58 +179,31 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= -github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= -github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= -github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= -github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= -github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= -github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= -github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= -github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -310,46 +211,24 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= -github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog= github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= -github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -357,17 +236,6 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= -github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -378,64 +246,41 @@ github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZ github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= -github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= +github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= +github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= +github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= -github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= @@ -443,25 +288,16 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= -github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= +github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 h1:XC9N1eiAyO1zg62dpOU8bex8emB/zluUtKcbLNjJxGI= github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484/go.mod h1:5nDZF4afNA1S7ZKcBXCMvDo4nuCTp1931DND7/W4aXo= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= @@ -469,17 +305,12 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -487,10 +318,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -506,9 +334,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= @@ -516,8 +343,8 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= -github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -525,20 +352,17 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -549,57 +373,24 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= -github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4= -github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= +github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= -github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= -github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/h2non/filetype v1.0.6 h1:g84/+gdkAT1hnYO+tHpCLoikm13Ju55OkN4KCb1uGEQ= github.com/h2non/filetype v1.0.6/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= -github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e h1:3YKHER4nmd7b5qy5t0GWDTwSn4OyRgfAXSmo6VnryBY= -github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e/go.mod h1:I8h3MITA53gN9OnWGCgaMa0JWVRdXthWw4M3CPM54OY= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -607,36 +398,21 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -648,272 +424,41 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= -github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= -github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= -github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= -github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= -github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= -github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= -github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= -github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= -github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= -github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= -github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= -github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= -github.com/ipfs/go-block-format v0.1.1 h1:129vSO3zwbsYADcyQWcOYiuCpAqt462SFfqFHdFJhhI= -github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= -github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= -github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= -github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= -github.com/ipfs/go-blockservice v0.5.1 h1:9pAtkyKAz/skdHTh0kH8VulzWp+qmSDD0aI17TYP/s0= -github.com/ipfs/go-blockservice v0.5.1/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= -github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= -github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= -github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= -github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= -github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= -github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= -github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= -github.com/ipfs/go-cidutil v0.1.0 h1:RW5hO7Vcf16dplUU60Hs0AKDkQAVPVplr7lk97CFL+Q= -github.com/ipfs/go-cidutil v0.1.0/go.mod h1:e7OEVBMIv9JaOxt9zaGEmAoSlXW9jdFZ5lP/0PwcfpA= -github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= -github.com/ipfs/go-datastore v0.0.5/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= -github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= -github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= -github.com/ipfs/go-datastore v0.3.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= -github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= -github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= -github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= -github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= -github.com/ipfs/go-delegated-routing v0.7.0 h1:43FyMnKA+8XnyX68Fwg6aoGkqrf8NS5aG7p644s26PU= -github.com/ipfs/go-delegated-routing v0.7.0/go.mod h1:u4zxjUWIe7APUW5ds9CfD0tJX3vM9JhIeNqA8kE4vHE= -github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= -github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= -github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= -github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= -github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= -github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= -github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= -github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= -github.com/ipfs/go-ds-flatfs v0.5.1 h1:ZCIO/kQOS/PSh3vcF1H6a8fkRGS7pOfwfPdx4n/KJH4= -github.com/ipfs/go-ds-flatfs v0.5.1/go.mod h1:RWTV7oZD/yZYBKdbVIFXTX2fdY2Tbvl94NsWqmoyAX4= -github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= -github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= -github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= -github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= -github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= -github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= -github.com/ipfs/go-ds-measure v0.2.0 h1:sG4goQe0KDTccHMyT45CY1XyUbxe5VwTKpg2LjApYyQ= -github.com/ipfs/go-ds-measure v0.2.0/go.mod h1:SEUD/rE2PwRa4IQEC5FuNAmjJCyYObZr9UvVh8V3JxE= -github.com/ipfs/go-fetcher v1.6.1 h1:UFuRVYX5AIllTiRhi5uK/iZkfhSpBCGX7L70nSZEmK8= -github.com/ipfs/go-fetcher v1.6.1/go.mod h1:27d/xMV8bodjVs9pugh/RCjjK2OZ68UgAMspMdingNo= -github.com/ipfs/go-filestore v1.2.0 h1:O2wg7wdibwxkEDcl7xkuQsPvJFRBVgVSsOJ/GP6z3yU= -github.com/ipfs/go-filestore v1.2.0/go.mod h1:HLJrCxRXquTeEEpde4lTLMaE/MYJZD7WHLkp9z6+FF8= -github.com/ipfs/go-fs-lock v0.0.7 h1:6BR3dajORFrFTkb5EpCUFIAypsoxpGpDSVUdFwzgL9U= -github.com/ipfs/go-fs-lock v0.0.7/go.mod h1:Js8ka+FNYmgQRLrRXzU3CB/+Csr1BwrRilEcvYrHhhc= -github.com/ipfs/go-graphsync v0.14.1 h1:tvFpBY9LcehIB7zi5SZIa+7aoxBOrGbdekhOXdnlT70= -github.com/ipfs/go-graphsync v0.14.1/go.mod h1:S6O/c5iXOXqDgrQgiZSgOTRUSiVvpKEhrzqFHKnLVcs= -github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= -github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= -github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= -github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= -github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= -github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= -github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= -github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= -github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= -github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8= -github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= -github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= -github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= -github.com/ipfs/go-ipfs-ds-help v0.0.1/go.mod h1:gtP9xRaZXqIQRh1HRpp595KbBEdgqWFxefeVKOV8sxo= -github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= -github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= -github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= -github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM= -github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= -github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= -github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= -github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAzpUws3x7UeEGkzQc3iNkM0= -github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= -github.com/ipfs/go-ipfs-exchange-offline v0.2.0/go.mod h1:HjwBeW0dvZvfOMwDP0TSKXIHf2s+ksdP4E3MLDRtLKY= -github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= -github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= -github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= -github.com/ipfs/go-ipfs-keystore v0.1.0 h1:gfuQUO/cyGZgZIHE6OrJas4OnwuxXCqJG7tI0lrB5Qc= -github.com/ipfs/go-ipfs-keystore v0.1.0/go.mod h1:LvLw7Qhnb0RlMOfCzK6OmyWxICip6lQ06CCmdbee75U= -github.com/ipfs/go-ipfs-pinner v0.3.0 h1:jwe5ViX3BON3KgOAYrrhav2+1ONB0QzFAWQd7HUlbuM= -github.com/ipfs/go-ipfs-pinner v0.3.0/go.mod h1:oX0I0nC6zlNIh0LslSrUnjfNKPq8ufoFtqV1/wcJvyo= -github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs= -github.com/ipfs/go-ipfs-posinfo v0.0.1/go.mod h1:SwyeVP+jCwiDu0C313l/8jg6ZxM0qqtlt2a0vILTc1A= -github.com/ipfs/go-ipfs-pq v0.0.1/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= -github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= -github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= -github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= -github.com/ipfs/go-ipfs-provider v0.8.1 h1:qt670pYmcNH3BCjyXDgg07o2WsTRsOdMwYc25ukCdjQ= -github.com/ipfs/go-ipfs-provider v0.8.1/go.mod h1:qCpwpoohIRVXvNzkygzsM3qdqP/sXlrogtA5I45tClc= -github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= -github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= -github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= -github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= -github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= -github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= -github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= -github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= -github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= -github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= -github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= -github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA= -github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= -github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k= -github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= -github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= -github.com/ipfs/go-ipld-format v0.4.0 h1:yqJSaJftjmjc9jEOFYlpkwOLVKv68OD27jFLlSghBlQ= -github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= -github.com/ipfs/go-ipld-git v0.1.1 h1:TWGnZjS0htmEmlMFEkA3ogrNCqWjIxwr16x1OsdhG+Y= -github.com/ipfs/go-ipld-git v0.1.1/go.mod h1:+VyMqF5lMcJh4rwEppV0e6g4nCCHXThLYYDpKUkJubI= -github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= -github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2cdcc= -github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= -github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A= -github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= -github.com/ipfs/go-libipfs v0.6.2 h1:QUf3kS3RrCjgtE0QW2d18PFFfOLeEt24Ft892ipLzRI= -github.com/ipfs/go-libipfs v0.6.2/go.mod h1:FmhKgxMOQA572TK5DA3MZ5GL44ZqsMHIrkgK4gLn4A8= -github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= -github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= -github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= -github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= -github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= -github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= -github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= -github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= -github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= -github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= -github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= -github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= -github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= -github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= -github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= -github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= -github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= -github.com/ipfs/go-merkledag v0.6.0/go.mod h1:9HSEwRd5sV+lbykiYP+2NC/3o6MZbKNaa4hfNcH5iH0= -github.com/ipfs/go-merkledag v0.9.0 h1:DFC8qZ96Dz1hMT7dtIpcY524eFFDiEWAF8hNJHWW2pk= -github.com/ipfs/go-merkledag v0.9.0/go.mod h1:bPHqkHt5OZ0p1n3iqPeDiw2jIBkjAytRjS3WSBwjq90= -github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= -github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= -github.com/ipfs/go-mfs v0.2.1 h1:5jz8+ukAg/z6jTkollzxGzhkl3yxm022Za9f2nL5ab8= -github.com/ipfs/go-mfs v0.2.1/go.mod h1:Woj80iuw4ajDnIP6+seRaoHpPsc9hmL0pk/nDNDWP88= -github.com/ipfs/go-namesys v0.7.0 h1:xqosk71GIVRkFDtF2UNRcXn4LdNeo7tzuy8feHD6NbU= -github.com/ipfs/go-namesys v0.7.0/go.mod h1:KYSZBVZG3VJC34EfqqJPG7T48aWgxseoMPAPA5gLyyQ= -github.com/ipfs/go-path v0.2.1/go.mod h1:NOScsVgxfC/eIw4nz6OiGwK42PjaSJ4Y/ZFPn1Xe07I= -github.com/ipfs/go-path v0.3.1 h1:wkeaCWE/NTuuPGlEkLTsED5UkzfKYZpxaFFPgk8ZVLE= -github.com/ipfs/go-path v0.3.1/go.mod h1:eNLsxJEEMxn/CDzUJ6wuNl+6No6tEUhOZcPKsZsYX0E= -github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= -github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= -github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= -github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= -github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= -github.com/ipfs/go-unixfs v0.3.1/go.mod h1:h4qfQYzghiIc8ZNFKiLMFWOTzrWIAtzYQ59W/pCFf1o= -github.com/ipfs/go-unixfs v0.4.4 h1:D/dLBOJgny5ZLIur2vIXVQVW0EyDHdOMBDEhgHrt6rY= -github.com/ipfs/go-unixfs v0.4.4/go.mod h1:TSG7G1UuT+l4pNj91raXAPkX0BhJi3jST1FDTfQ5QyM= -github.com/ipfs/go-unixfsnode v1.1.2/go.mod h1:5dcE2x03pyjHk4JjamXmunTMzz+VUtqvPwZjIEkfV6s= -github.com/ipfs/go-unixfsnode v1.5.2 h1:CvsiTt58W2uR5dD8bqQv+aAY0c1qolmXmSyNbPHYiew= -github.com/ipfs/go-unixfsnode v1.5.2/go.mod h1:NlOebRwYx8lMCNMdhAhEspYPBD3obp7TE0LvBqHY+ks= -github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= -github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= -github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= -github.com/ipfs/interface-go-ipfs-core v0.11.0 h1:n1tplrwsz7oZXkpkZM5a3MDBxksMfSQ103ej4e+l7NA= -github.com/ipfs/interface-go-ipfs-core v0.11.0/go.mod h1:xmnoccUXY7N/Q8AIx0vFqgW926/FAZ8+do/1NTEHKsU= -github.com/ipfs/kubo v0.19.1 h1:jQmwct9gurfZcpShmfwZf/0CXSgxgTVWJxx//l4Ob3M= -github.com/ipfs/kubo v0.19.1/go.mod h1:jD1cb+H5ax9EzxLflHG8dz5LHfuAMO+r00/h3MwYkd4= -github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk= -github.com/ipld/edelweiss v0.2.0/go.mod h1:FJAzJRCep4iI8FOFlRriN9n0b7OuX3T/S9++NpBDmA4= -github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= -github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk= -github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= -github.com/ipld/go-codec-dagpb v1.5.0 h1:RspDRdsJpLfgCI0ONhTAnbHdySGD4t+LHSPK4X1+R0k= -github.com/ipld/go-codec-dagpb v1.5.0/go.mod h1:0yRIutEFD8o1DGVqw4RSHh+BUTlJA9XWldxaaWR/o4g= -github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= -github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= -github.com/ipld/go-ipld-prime v0.14.1/go.mod h1:QcE4Y9n/ZZr8Ijg5bGPT0GqYWgZ1704nH0RDcQtgTP0= -github.com/ipld/go-ipld-prime v0.19.0 h1:5axC7rJmPc17Emw6TelxGwnzALk0PdupZ2oj2roDj04= -github.com/ipld/go-ipld-prime v0.19.0/go.mod h1:Q9j3BaVXwaA3o5JUDNvptDDr/x8+F7FG6XJ8WI3ILg4= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= -github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= -github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc= -github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= -github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c h1:uUx61FiAa1GI6ZmVd2wf2vULeQZIKG66eybjNXKYCz4= -github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= -github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= -github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= -github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= -github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= -github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= -github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9 h1:Y+lzErDTURqeXqlqYi4YBYbDd7ycU74gW1ADt57/bgY= github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/retry v0.0.0-20160928201858-1998d01ba1c3/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0 h1:+WWUkhnTjV6RNOxkcwk79qrjeyHEHvBzlneueBsatX4= github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbNBraRLZEhoQwFLMrjK8PSlO4D3nDjKYXo= @@ -922,349 +467,78 @@ github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CIm github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= -github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knadh/koanf v1.4.0 h1:/k0Bh49SqLyLNfte9r6cvuZWrApOQhglOmhIU3L/zDw= github.com/knadh/koanf v1.4.0/go.mod h1:1cfH5223ZeZUOs8FU2UdTmaNfHpqgtjV0+NHjRO43gs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= -github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= -github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= -github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= -github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= -github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= -github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= -github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= -github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= -github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= -github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= -github.com/libp2p/go-doh-resolver v0.4.0 h1:gUBa1f1XsPwtpE1du0O+nnZCUqtG7oYi7Bb+0S7FQqw= -github.com/libp2p/go-doh-resolver v0.4.0/go.mod h1:v1/jwsFusgsWIGX/c6vCRrnJ60x7bhTiq/fs2qt0cAg= -github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= -github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= -github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= -github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= -github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= -github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM= -github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8= -github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= -github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= -github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= -github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= -github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= -github.com/libp2p/go-libp2p v0.27.8 h1:IX5x/4yKwyPQeVS2AXHZ3J4YATM9oHBGH1gBc23jBAI= -github.com/libp2p/go-libp2p v0.27.8/go.mod h1:eCFFtd0s5i/EVKR7+5Ki8bM7qwkNW3TPTTSSW9sz8NE= -github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= -github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= -github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= -github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= -github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= -github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= -github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= -github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= -github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= -github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= -github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= -github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= -github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= -github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= -github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= -github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= -github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= -github.com/libp2p/go-libp2p-core v0.0.3/go.mod h1:j+YQMNz9WNSkNezXOsahp9kwZBKBvxLpKD316QWSJXE= -github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= -github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= -github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= -github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= -github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= -github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= -github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= -github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= -github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= -github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= -github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= -github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= -github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= -github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.21.1 h1:xpfp8/t9+X2ip1l8Umap1/UGNnJ3RHJgKGAEsnRAlTo= -github.com/libp2p/go-libp2p-kad-dht v0.21.1/go.mod h1:Oy8wvbdjpB70eS5AaFaI68tOtrdo3KylTvXDjikxqFo= -github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= -github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= -github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= -github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= -github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= -github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= -github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= -github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= -github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= -github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= -github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= -github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= -github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= -github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= -github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= -github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= -github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= -github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= -github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= -github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= -github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= -github.com/libp2p/go-libp2p-pubsub v0.9.0 h1:mcLb4WzwhUG4OKb0rp1/bYMd/DYhvMyzJheQH3LMd1s= -github.com/libp2p/go-libp2p-pubsub v0.9.0/go.mod h1:OEsj0Cc/BpkqikXRTrVspWU/Hx7bMZwHP+6vNMd+c7I= -github.com/libp2p/go-libp2p-pubsub-router v0.6.0 h1:D30iKdlqDt5ZmLEYhHELCMRj8b4sFAqrUcshIUvVP/s= -github.com/libp2p/go-libp2p-pubsub-router v0.6.0/go.mod h1:FY/q0/RBTKsLA7l4vqC2cbRbOvyDotg8PJQ7j8FDudE= -github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= -github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= -github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= -github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= -github.com/libp2p/go-libp2p-routing-helpers v0.6.2 h1:u6SWfX+3LoqqTAFxWVl79RkcIDE3Zsay5d+JohlEBaE= -github.com/libp2p/go-libp2p-routing-helpers v0.6.2/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= -github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= -github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= -github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= -github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= -github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= -github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= -github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= -github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= -github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= -github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= -github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= -github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= -github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= -github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= -github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= -github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= -github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= -github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= -github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= -github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= -github.com/libp2p/go-libp2p-xor v0.1.0 h1:hhQwT4uGrBcuAkUGXADuPltalOdpf9aag9kaYNT2tLA= -github.com/libp2p/go-libp2p-xor v0.1.0/go.mod h1:LSTM5yRnjGZbWNTA/hRwq2gGFrvRIbQJscoIL/u6InY= -github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= -github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= -github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= -github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= -github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= -github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= -github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= -github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= -github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= -github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= -github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= -github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= -github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= -github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= -github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= -github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= -github.com/libp2p/go-mplex v0.7.0 h1:BDhFZdlk5tbr0oyFq/xv/NPGfjbnrsDam1EvutpBDbY= -github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= -github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-msgio v0.0.3/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= -github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= -github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= -github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= -github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= -github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= -github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= -github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= -github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= -github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= -github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= -github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= -github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= -github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= -github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= -github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= -github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= -github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= -github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= -github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= -github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= -github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= -github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= -github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= -github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= -github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc= -github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= -github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= -github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= -github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= -github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= -github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= -github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= -github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f h1:4+gHs0jJFJ06bfN8PshnM6cHcxGjRUVRLo5jndDiKRQ= github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f/go.mod h1:tHCZHV8b2A90ObojrEAzY0Lb03gxUxjDHr5IJyAh4ew= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= -github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= -github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw= -github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= -github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= -github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= -github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= -github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= -github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= -github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= -github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= -github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= -github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -1274,155 +548,42 @@ github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iP github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= -github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= -github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= -github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= -github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= -github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= -github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= -github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= -github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= -github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= -github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= -github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= -github.com/multiformats/go-multiaddr v0.12.1 h1:vm+BA/WZA8QZDp1pF1FWhi5CT3g1tbi5GJmqpb6wnlk= -github.com/multiformats/go-multiaddr v0.12.1/go.mod h1:7mPkiBMmLeFipt+nNSq9pHZUeJSt8lHBgH6yhj0YQzE= -github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= -github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= -github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= -github.com/multiformats/go-multiaddr-dns v0.3.0/go.mod h1:mNzQ4eTGDg0ll1N9jKPOUogZPoJ30W8a7zk66FQPpdQ= -github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= -github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= -github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= -github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= -github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= -github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= -github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= -github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= -github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= -github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= -github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= -github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= -github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= -github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= -github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= -github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= -github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= -github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= -github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= -github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= -github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= -github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= -github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= -github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= -github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= -github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= -github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= -github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw= -github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1430,76 +591,45 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= -github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= -github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= -github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= -github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= -github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.3 h1:wznEHvJwd+2X3PqftRha0SUKmGsnb6dfArMhy9PeJVE= -github.com/quic-go/qtls-go1-19 v0.3.3/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.3 h1:m575dovXn1y2ATOb1XrRFcrv0F+EQmlowTkoraNkDPI= -github.com/quic-go/qtls-go1-20 v0.2.3/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= -github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= -github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= -github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= -github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= -github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rhnvrm/simples3 v0.6.1 h1:H0DJwybR6ryQE+Odi9eqkHuzjYAeJgtGcGtuBwOhsH8= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= -github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703 h1:ZyM/+FYnpbZsFWuCohniM56kRoHRB4r5EuIzXEYkpxo= -github.com/rivo/tview v0.0.0-20230814110005-ccc2c8119703/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE= +github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 h1:bWLHTRekAy497pE7+nXSuzXwwFHI0XauRzz6roUvY+s= +github.com/rivo/tview v0.0.0-20240307173318-e804876934a1/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= @@ -1509,178 +639,73 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= -github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= -github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= -github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= -github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= -github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= -github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= -github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= -github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= -github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= -github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= -github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= -github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= -github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= -github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= -github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= -github.com/warpfork/go-testmark v0.9.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= -github.com/warpfork/go-testmark v0.10.0 h1:E86YlUMYfwIacEsQGlnTvjk1IgYkyTGjPhF0RnwTCmw= -github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= -github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= -github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= -github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= -github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/wasmerio/wasmer-go v1.0.4 h1:MnqHoOGfiQ8MMq2RF6wyCeebKOe84G88h5yv+vmxJgs= +github.com/wasmerio/wasmer-go v1.0.4/go.mod h1:0gzVdSfg6pysA6QVp6iVRPTagC6Wq9pOE8J86WKb2Fk= github.com/wealdtech/go-merkletree v1.0.0 h1:DsF1xMzj5rK3pSQM6mPv8jlyJyHXhFxpnA2bwEjMMBY= github.com/wealdtech/go-merkletree v1.0.0/go.mod h1:cdil512d/8ZC7Kx3bfrDvGMQXB25NTKbsm0rFrmDax4= -github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc h1:BCPnHtcboadS0DvysUuJXZ4lWVv5Bh5i7+tbIyi+ck4= -github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= -github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= -github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= -github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= -github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= -github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= -github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= -github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= -github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= -github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= -github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= -github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= -github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= -github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= -github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= -github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= -github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= @@ -1694,111 +719,26 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= -go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= -go.opentelemetry.io/otel/exporters/jaeger v1.7.0 h1:wXgjiRldljksZkZrldGVe6XrG9u3kYDyQmkZwmm5dI0= -go.opentelemetry.io/otel/exporters/jaeger v1.7.0/go.mod h1:PwQAOqBgqbLQRKlj466DuD2qyMjbtcPpfPfj+AqbSBs= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 h1:7Yxsak1q4XrJ5y7XBnNwqWx9amMZvoidCctv62XOQ6Y= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0/go.mod h1:M1hVZHNxcbkAlcvrOMlpQ4YOO3Awf+4N2dxkZL3xm04= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 h1:cMDtmgJ5FpRvqx9x2Aq+Mm0O6K/zcUkH73SFz20TuBw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0/go.mod h1:ceUgdyfNv4h4gLxHR0WNfDiiVmZFodZhZSbOLhpxqXE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 h1:MFAyzUPrTwLOwCi+cltN0ZVyy4phU41lwH+lyMyQTS4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0/go.mod h1:E+/KKhwOSw8yoPxSSuUHG6vKppkvhN+S1Jc7Nib3k3o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.7.0 h1:pLP0MH4MAqeTEV0g/4flxw9O8Is48uAIauAnjznbW50= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.7.0/go.mod h1:aFXT9Ng2seM9eizF+LfKiyPBGy8xIZKwhusC1gIu3hA= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.7.0 h1:8hPcgCg0rUJiKE6VWahRvjgLUrNl7rW2hffUEPKXVEM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.7.0/go.mod h1:K4GDXPY6TjUiwbOh+DkKaEdCF8y+lvMoM6SeAPyfCCM= -go.opentelemetry.io/otel/exporters/zipkin v1.7.0 h1:X0FZj+kaIdLi29UiyrEGDhRTYsEXj9GdEW5Y39UQFEE= -go.opentelemetry.io/otel/exporters/zipkin v1.7.0/go.mod h1:9YBXeOMFLQGwNEjsxMRiWPGoJX83usGMhbCmxUbNe5I= -go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= -go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= -go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= -go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.16.0 h1:WHzDWdXUvbc5bG2ObdrGfaNpQz7ft7QN9HHmJlbiB1E= -go.opentelemetry.io/proto/otlp v0.16.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= -go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= -go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= -go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= -go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= -go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= -golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= -golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= @@ -1816,7 +756,6 @@ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1838,40 +777,28 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1889,27 +816,23 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1922,59 +845,41 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190524122548-abf6ff778158/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1986,32 +891,25 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2024,14 +922,15 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2045,19 +944,13 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2075,18 +968,13 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2106,24 +994,16 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -2141,8 +1021,6 @@ google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -2150,16 +1028,11 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -2178,7 +1051,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -2186,46 +1058,22 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI= -google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2238,10 +1086,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2251,33 +1097,22 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= -gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -2289,8 +1124,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2298,19 +1131,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= -lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= -pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= -sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/nitro-testnode b/nitro-testnode deleted file mode 160000 index 8302b1148..000000000 --- a/nitro-testnode +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8302b1148d19d96300171ad2dfd0f7a686674abe diff --git a/precompiles/ArbDebug.go b/precompiles/ArbDebug.go index ef059db3f..bf85d5e18 100644 --- a/precompiles/ArbDebug.go +++ b/precompiles/ArbDebug.go @@ -48,7 +48,7 @@ func (con ArbDebug) EventsView(c ctx, evm mech) error { } func (con ArbDebug) CustomRevert(c ctx, number uint64) error { - return con.CustomError(number, "This spider family wards off bugs: /\\oo/\\ //\\(oo)/\\ /\\oo/\\", true) + return con.CustomError(number, "This spider family wards off bugs: /\\oo/\\ //\\(oo)//\\ /\\oo/\\", true) } // Caller becomes a chain owner @@ -56,6 +56,11 @@ func (con ArbDebug) BecomeChainOwner(c ctx, evm mech) error { return c.State.ChainOwners().Add(c.caller) } +// Halts the chain by panicking in the STF +func (con ArbDebug) Panic(c ctx, evm mech) error { + panic("called ArbDebug's debug-only Panic method") +} + func (con ArbDebug) LegacyError(c ctx) error { return errors.New("example legacy error") } diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index cda5350a4..b41dfda8a 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -36,7 +36,12 @@ func (con ArbGasInfo) GetPricesInWeiWithAggregator( if err != nil { return nil, nil, nil, nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeInBlock != nil { + l2GasPrice = evm.Context.BaseFeeInBlock + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -69,7 +74,12 @@ func (con ArbGasInfo) _preVersion4_GetPricesInWeiWithAggregator( if err != nil { return nil, nil, nil, nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeInBlock != nil { + l2GasPrice = evm.Context.BaseFeeInBlock + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -101,7 +111,12 @@ func (con ArbGasInfo) GetPricesInArbGasWithAggregator(c ctx, evm mech, aggregato if err != nil { return nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeInBlock != nil { + l2GasPrice = evm.Context.BaseFeeInBlock + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -121,7 +136,12 @@ func (con ArbGasInfo) _preVersion4_GetPricesInArbGasWithAggregator(c ctx, evm me if err != nil { return nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeInBlock != nil { + l2GasPrice = evm.Context.BaseFeeInBlock + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -187,7 +207,7 @@ func (con ArbGasInfo) GetGasBacklog(c ctx, evm mech) (uint64, error) { return c.State.L2PricingState().GasBacklog() } -// GetPricingInertia gets the L2 basefee in response to backlogged gas +// GetPricingInertia gets how slowly ArbOS updates the L2 basefee in response to backlogged gas func (con ArbGasInfo) GetPricingInertia(c ctx, evm mech) (uint64, error) { return c.State.L2PricingState().PricingInertia() } @@ -197,25 +217,13 @@ func (con ArbGasInfo) GetGasBacklogTolerance(c ctx, evm mech) (uint64, error) { return c.State.L2PricingState().BacklogTolerance() } +// GetL1PricingSurplus gets the surplus of funds for L1 batch posting payments (may be negative) func (con ArbGasInfo) GetL1PricingSurplus(c ctx, evm mech) (*big.Int, error) { if c.State.ArbOSVersion() < 10 { return con._preversion10_GetL1PricingSurplus(c, evm) } ps := c.State.L1PricingState() - fundsDueForRefunds, err := ps.BatchPosterTable().TotalFundsDue() - if err != nil { - return nil, err - } - fundsDueForRewards, err := ps.FundsDueForRewards() - if err != nil { - return nil, err - } - haveFunds, err := ps.L1FeesAvailable() - if err != nil { - return nil, err - } - needFunds := arbmath.BigAdd(fundsDueForRefunds, fundsDueForRewards) - return arbmath.BigSub(haveFunds, needFunds), nil + return ps.GetL1PricingSurplus() } func (con ArbGasInfo) _preversion10_GetL1PricingSurplus(c ctx, evm mech) (*big.Int, error) { @@ -230,37 +238,45 @@ func (con ArbGasInfo) _preversion10_GetL1PricingSurplus(c ctx, evm mech) (*big.I } haveFunds := evm.StateDB.GetBalance(l1pricing.L1PricerFundsPoolAddress) needFunds := arbmath.BigAdd(fundsDueForRefunds, fundsDueForRewards) - return arbmath.BigSub(haveFunds, needFunds), nil + return arbmath.BigSub(haveFunds.ToBig(), needFunds), nil } +// GetPerBatchGasCharge gets the base charge (in L1 gas) attributed to each data batch in the calldata pricer func (con ArbGasInfo) GetPerBatchGasCharge(c ctx, evm mech) (int64, error) { return c.State.L1PricingState().PerBatchGasCost() } +// GetAmortizedCostCapBips gets the cost amortization cap in basis points func (con ArbGasInfo) GetAmortizedCostCapBips(c ctx, evm mech) (uint64, error) { return c.State.L1PricingState().AmortizedCostCapBips() } +// GetL1FeesAvailable gets the available funds from L1 fees func (con ArbGasInfo) GetL1FeesAvailable(c ctx, evm mech) (huge, error) { return c.State.L1PricingState().L1FeesAvailable() } +// GetL1PricingEquilibrationUnits gets the equilibration units parameter for L1 price adjustment algorithm func (con ArbGasInfo) GetL1PricingEquilibrationUnits(c ctx, evm mech) (*big.Int, error) { return c.State.L1PricingState().EquilibrationUnits() } +// GetLastL1PricingUpdateTime gets the last time the L1 calldata pricer was updated func (con ArbGasInfo) GetLastL1PricingUpdateTime(c ctx, evm mech) (uint64, error) { return c.State.L1PricingState().LastUpdateTime() } +// GetL1PricingFundsDueForRewards gets the amount of L1 calldata payments due for rewards (per the L1 reward rate) func (con ArbGasInfo) GetL1PricingFundsDueForRewards(c ctx, evm mech) (*big.Int, error) { return c.State.L1PricingState().FundsDueForRewards() } +// GetL1PricingUnitsSinceUpdate gets the amount of L1 calldata posted since the last update func (con ArbGasInfo) GetL1PricingUnitsSinceUpdate(c ctx, evm mech) (uint64, error) { return c.State.L1PricingState().UnitsSinceUpdate() } +// GetLastL1PricingSurplus gets the L1 pricing surplus as of the last update (may be negative) func (con ArbGasInfo) GetLastL1PricingSurplus(c ctx, evm mech) (*big.Int, error) { return c.State.L1PricingState().LastSurplus() } diff --git a/precompiles/ArbInfo.go b/precompiles/ArbInfo.go index a260f7e7a..9f8cf3453 100644 --- a/precompiles/ArbInfo.go +++ b/precompiles/ArbInfo.go @@ -18,7 +18,7 @@ func (con ArbInfo) GetBalance(c ctx, evm mech, account addr) (huge, error) { if err := c.Burn(params.BalanceGasEIP1884); err != nil { return nil, err } - return evm.StateDB.GetBalance(account), nil + return evm.StateDB.GetBalance(account).ToBig(), nil } // GetCode retrieves a contract's deployed code diff --git a/precompiles/ArbOwner.go b/precompiles/ArbOwner.go index 166768940..066fc0a4c 100644 --- a/precompiles/ArbOwner.go +++ b/precompiles/ArbOwner.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package precompiles @@ -11,6 +11,9 @@ import ( "math/big" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/programs" + "github.com/offchainlabs/nitro/util/arbmath" + am "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" @@ -153,7 +156,7 @@ func (con ArbOwner) ReleaseL1PricerSurplusFunds(c ctx, evm mech, maxWeiToRelease if err != nil { return nil, err } - weiToTransfer := new(big.Int).Sub(balance, recognized) + weiToTransfer := new(big.Int).Sub(balance.ToBig(), recognized) if weiToTransfer.Sign() < 0 { return common.Big0, nil } @@ -166,6 +169,129 @@ func (con ArbOwner) ReleaseL1PricerSurplusFunds(c ctx, evm mech, maxWeiToRelease return weiToTransfer, nil } +// Sets the amount of ink 1 gas buys +func (con ArbOwner) SetInkPrice(c ctx, evm mech, inkPrice uint32) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + ink, err := arbmath.IntToUint24(inkPrice) + if err != nil || ink == 0 { + return errors.New("ink price must be a positive uint24") + } + params.InkPrice = ink + return params.Save() +} + +// Sets the maximum depth (in wasm words) a wasm stack may grow +func (con ArbOwner) SetWasmMaxStackDepth(c ctx, evm mech, depth uint32) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.MaxStackDepth = depth + return params.Save() +} + +// Gets the number of free wasm pages a tx gets +func (con ArbOwner) SetWasmFreePages(c ctx, evm mech, pages uint16) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.FreePages = pages + return params.Save() +} + +// Sets the base cost of each additional wasm page +func (con ArbOwner) SetWasmPageGas(c ctx, evm mech, gas uint16) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.PageGas = gas + return params.Save() +} + +// Sets the initial number of pages a wasm may allocate +func (con ArbOwner) SetWasmPageLimit(c ctx, evm mech, limit uint16) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.PageLimit = limit + return params.Save() +} + +// Sets the minimum costs to invoke a program +func (con ArbOwner) SetWasmMinInitGas(c ctx, _ mech, gas, cached uint64) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.MinInitGas = am.SaturatingUUCast[uint8](am.DivCeil(gas, programs.MinInitGasUnits)) + params.MinCachedInitGas = am.SaturatingUUCast[uint8](am.DivCeil(cached, programs.MinCachedGasUnits)) + return params.Save() +} + +// Sets the linear adjustment made to program init costs +func (con ArbOwner) SetWasmInitCostScalar(c ctx, _ mech, percent uint64) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.InitCostScalar = am.SaturatingUUCast[uint8](am.DivCeil(percent, programs.CostScalarPercent)) + return params.Save() +} + +// Sets the number of days after which programs deactivate +func (con ArbOwner) SetWasmExpiryDays(c ctx, _ mech, days uint16) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.ExpiryDays = days + return params.Save() +} + +// Sets the age a program must be to perform a keepalive +func (con ArbOwner) SetWasmKeepaliveDays(c ctx, _ mech, days uint16) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.KeepaliveDays = days + return params.Save() +} + +// Sets the number of extra programs ArbOS caches during a given block +func (con ArbOwner) SetWasmBlockCacheSize(c ctx, _ mech, count uint16) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + params.BlockCacheSize = count + return params.Save() +} + +// Adds account as a wasm cache manager +func (con ArbOwner) AddWasmCacheManager(c ctx, _ mech, manager addr) error { + return c.State.Programs().CacheManagers().Add(manager) +} + +// Removes account from the list of wasm cache managers +func (con ArbOwner) RemoveWasmCacheManager(c ctx, _ mech, manager addr) error { + managers := c.State.Programs().CacheManagers() + isMember, err := managers.IsMember(manager) + if err != nil { + return err + } + if !isMember { + return errors.New("tried to remove non-manager") + } + return managers.Remove(manager, c.State.ArbOSVersion()) +} + func (con ArbOwner) SetChainConfig(c ctx, evm mech, serializedChainConfig []byte) error { if c == nil { return errors.New("nil context") diff --git a/precompiles/ArbOwner_test.go b/precompiles/ArbOwner_test.go index ab128a8cb..1f8c7ae4c 100644 --- a/precompiles/ArbOwner_test.go +++ b/precompiles/ArbOwner_test.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/burn" @@ -113,7 +114,7 @@ func TestArbOwner(t *testing.T) { Fail(t, avail) } deposited := big.NewInt(1000000) - evm.StateDB.AddBalance(l1pricing.L1PricerFundsPoolAddress, deposited) + evm.StateDB.AddBalance(l1pricing.L1PricerFundsPoolAddress, uint256.MustFromBig(deposited)) avail, err = gasInfo.GetL1FeesAvailable(callCtx, evm) Require(t, err) if avail.Sign() != 0 { diff --git a/precompiles/ArbRetryableTx.go b/precompiles/ArbRetryableTx.go index 3cb7510f0..d508d7575 100644 --- a/precompiles/ArbRetryableTx.go +++ b/precompiles/ArbRetryableTx.go @@ -127,7 +127,7 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er // Add the gasToDonate back to the gas pool: the retryable attempt will then consume it. // This ensures that the gas pool has enough gas to run the retryable attempt. - return retryTxHash, c.State.L2PricingState().AddToGasPool(arbmath.SaturatingCast(gasToDonate)) + return retryTxHash, c.State.L2PricingState().AddToGasPool(arbmath.SaturatingCast[int64](gasToDonate)) } // GetLifetime gets the default lifetime period a retryable has at creation diff --git a/precompiles/ArbSys.go b/precompiles/ArbSys.go index 0d3df3bbf..13f56d3b8 100644 --- a/precompiles/ArbSys.go +++ b/precompiles/ArbSys.go @@ -96,7 +96,7 @@ func (con *ArbSys) MyCallersAddressWithoutAliasing(c ctx, evm mech) (addr, error address := addr{} if evm.Depth() > 1 { - address = c.txProcessor.Callers[evm.Depth()-2] + address = c.txProcessor.Contracts[evm.Depth()-2].Caller() } aliased, err := con.WasMyCallersAddressAliased(c, evm) @@ -209,5 +209,5 @@ func (con ArbSys) WithdrawEth(c ctx, evm mech, value huge, destination addr) (hu func (con ArbSys) isTopLevel(c ctx, evm mech) bool { depth := evm.Depth() - return depth < 2 || evm.Origin == c.txProcessor.Callers[depth-2] + return depth < 2 || evm.Origin == c.txProcessor.Contracts[depth-2].Caller() } diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go new file mode 100644 index 000000000..9f42cacb5 --- /dev/null +++ b/precompiles/ArbWasm.go @@ -0,0 +1,224 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package precompiles + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbos/programs" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/util/arbmath" +) + +type ArbWasm struct { + Address addr // 0x71 + + ProgramActivated func(ctx, mech, hash, hash, addr, huge, uint16) error + ProgramActivatedGasCost func(hash, hash, addr, huge, uint16) (uint64, error) + ProgramLifetimeExtended func(ctx, mech, hash, huge) error + ProgramLifetimeExtendedGasCost func(hash, huge) (uint64, error) + + ProgramNotWasmError func() error + ProgramNotActivatedError func() error + ProgramNeedsUpgradeError func(version, stylusVersion uint16) error + ProgramExpiredError func(age uint64) error + ProgramUpToDateError func() error + ProgramKeepaliveTooSoonError func(age uint64) error + ProgramInsufficientValueError func(have, want huge) error +} + +// Compile a wasm program with the latest instrumentation +func (con ArbWasm) ActivateProgram(c ctx, evm mech, value huge, program addr) (uint16, huge, error) { + debug := evm.ChainConfig().DebugMode() + runMode := c.txProcessor.RunMode() + programs := c.State.Programs() + + // charge a fixed cost up front to begin activation + if err := c.Burn(1659168); err != nil { + return 0, nil, err + } + version, codeHash, moduleHash, dataFee, takeAllGas, err := programs.ActivateProgram(evm, program, runMode, debug) + if takeAllGas { + _ = c.BurnOut() + } + if err != nil { + return version, dataFee, err + } + if err := con.payActivationDataFee(c, evm, value, dataFee); err != nil { + return version, dataFee, err + } + return version, dataFee, con.ProgramActivated(c, evm, codeHash, moduleHash, program, dataFee, version) +} + +// Extends a program's expiration date (reverts if too soon) +func (con ArbWasm) CodehashKeepalive(c ctx, evm mech, value huge, codehash bytes32) error { + params, err := c.State.Programs().Params() + if err != nil { + return err + } + dataFee, err := c.State.Programs().ProgramKeepalive(codehash, evm.Context.Time, params) + if err != nil { + return err + } + if err := con.payActivationDataFee(c, evm, value, dataFee); err != nil { + return err + } + return con.ProgramLifetimeExtended(c, evm, codehash, dataFee) +} + +// Pays the data component of activation costs +func (con ArbWasm) payActivationDataFee(c ctx, evm mech, value, dataFee huge) error { + if arbmath.BigLessThan(value, dataFee) { + return con.ProgramInsufficientValueError(value, dataFee) + } + network, err := c.State.NetworkFeeAccount() + if err != nil { + return err + } + scenario := util.TracingDuringEVM + repay := arbmath.BigSub(value, dataFee) + + // transfer the fee to the network account, and the rest back to the user + err = util.TransferBalance(&con.Address, &network, dataFee, evm, scenario, "activate") + if err != nil { + return err + } + return util.TransferBalance(&con.Address, &c.caller, repay, evm, scenario, "reimburse") +} + +// Gets the latest stylus version +func (con ArbWasm) StylusVersion(c ctx, evm mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.Version, err +} + +// Gets the amount of ink 1 gas buys +func (con ArbWasm) InkPrice(c ctx, _ mech) (uint32, error) { + params, err := c.State.Programs().Params() + return params.InkPrice.ToUint32(), err +} + +// Gets the wasm stack size limit +func (con ArbWasm) MaxStackDepth(c ctx, _ mech) (uint32, error) { + params, err := c.State.Programs().Params() + return params.MaxStackDepth, err +} + +// Gets the number of free wasm pages a tx gets +func (con ArbWasm) FreePages(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.FreePages, err +} + +// Gets the base cost of each additional wasm page +func (con ArbWasm) PageGas(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.PageGas, err +} + +// Gets the ramp that drives exponential memory costs +func (con ArbWasm) PageRamp(c ctx, _ mech) (uint64, error) { + params, err := c.State.Programs().Params() + return params.PageRamp, err +} + +// Gets the maximum initial number of pages a wasm may allocate +func (con ArbWasm) PageLimit(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.PageLimit, err +} + +// Gets the minimum costs to invoke a program +func (con ArbWasm) MinInitGas(c ctx, _ mech) (uint64, uint64, error) { + params, err := c.State.Programs().Params() + init := uint64(params.MinInitGas) * programs.MinInitGasUnits + cached := uint64(params.MinCachedInitGas) * programs.MinCachedGasUnits + return init, cached, err +} + +// Gets the linear adjustment made to program init costs +func (con ArbWasm) InitCostScalar(c ctx, _ mech) (uint64, error) { + params, err := c.State.Programs().Params() + return uint64(params.InitCostScalar) * programs.CostScalarPercent, err +} + +// Gets the number of days after which programs deactivate +func (con ArbWasm) ExpiryDays(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.ExpiryDays, err +} + +// Gets the age a program must be to perform a keepalive +func (con ArbWasm) KeepaliveDays(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.KeepaliveDays, err +} + +// Gets the number of extra programs ArbOS caches during a given block. +func (con ArbWasm) BlockCacheSize(c ctx, _ mech) (uint16, error) { + params, err := c.State.Programs().Params() + return params.BlockCacheSize, err +} + +// Gets the stylus version that program with codehash was most recently compiled with +func (con ArbWasm) CodehashVersion(c ctx, evm mech, codehash bytes32) (uint16, error) { + params, err := c.State.Programs().Params() + if err != nil { + return 0, err + } + return c.State.Programs().CodehashVersion(codehash, evm.Context.Time, params) +} + +// Gets a program's asm size in bytes +func (con ArbWasm) CodehashAsmSize(c ctx, evm mech, codehash bytes32) (uint32, error) { + params, err := c.State.Programs().Params() + if err != nil { + return 0, err + } + return c.State.Programs().ProgramAsmSize(codehash, evm.Context.Time, params) +} + +// Gets the stylus version that program at addr was most recently compiled with +func (con ArbWasm) ProgramVersion(c ctx, evm mech, program addr) (uint16, error) { + codehash, err := c.GetCodeHash(program) + if err != nil { + return 0, err + } + return con.CodehashVersion(c, evm, codehash) +} + +// Gets the cost to invoke the program +func (con ArbWasm) ProgramInitGas(c ctx, evm mech, program addr) (uint64, uint64, error) { + codehash, params, err := con.getCodeHash(c, program) + if err != nil { + return 0, 0, err + } + return c.State.Programs().ProgramInitGas(codehash, evm.Context.Time, params) +} + +// Gets the footprint of program at addr +func (con ArbWasm) ProgramMemoryFootprint(c ctx, evm mech, program addr) (uint16, error) { + codehash, params, err := con.getCodeHash(c, program) + if err != nil { + return 0, err + } + return c.State.Programs().ProgramMemoryFootprint(codehash, evm.Context.Time, params) +} + +// Gets returns the amount of time remaining until the program expires +func (con ArbWasm) ProgramTimeLeft(c ctx, evm mech, program addr) (uint64, error) { + codehash, params, err := con.getCodeHash(c, program) + if err != nil { + return 0, err + } + return c.State.Programs().ProgramTimeLeft(codehash, evm.Context.Time, params) +} + +func (con ArbWasm) getCodeHash(c ctx, program addr) (hash, *programs.StylusParams, error) { + params, err := c.State.Programs().Params() + if err != nil { + return common.Hash{}, params, err + } + codehash, err := c.GetCodeHash(program) + return codehash, params, err +} diff --git a/precompiles/ArbWasmCache.go b/precompiles/ArbWasmCache.go new file mode 100644 index 000000000..36b4e1ad3 --- /dev/null +++ b/precompiles/ArbWasmCache.go @@ -0,0 +1,68 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package precompiles + +type ArbWasmCache struct { + Address addr // 0x72 + + UpdateProgramCache func(ctx, mech, addr, bytes32, bool) error + UpdateProgramCacheGasCost func(addr, bytes32, bool) (uint64, error) +} + +// See if the user is a cache manager owner. +func (con ArbWasmCache) IsCacheManager(c ctx, _ mech, addr addr) (bool, error) { + return c.State.Programs().CacheManagers().IsMember(addr) +} + +// Retrieve all authorized address managers. +func (con ArbWasmCache) AllCacheManagers(c ctx, _ mech) ([]addr, error) { + return c.State.Programs().CacheManagers().AllMembers(65536) +} + +// Caches all programs with the given codehash. Caller must be a cache manager or chain owner. +func (con ArbWasmCache) CacheCodehash(c ctx, evm mech, codehash hash) error { + return con.setProgramCached(c, evm, codehash, true) +} + +// Evicts all programs with the given codehash. Caller must be a cache manager or chain owner. +func (con ArbWasmCache) EvictCodehash(c ctx, evm mech, codehash hash) error { + return con.setProgramCached(c, evm, codehash, false) +} + +// Gets whether a program is cached. Note that the program may be expired. +func (con ArbWasmCache) CodehashIsCached(c ctx, evm mech, codehash hash) (bool, error) { + return c.State.Programs().ProgramCached(codehash) +} + +// Caches all programs with the given codehash. +func (con ArbWasmCache) setProgramCached(c ctx, evm mech, codehash hash, cached bool) error { + if !con.hasAccess(c) { + return c.BurnOut() + } + programs := c.State.Programs() + params, err := programs.Params() + if err != nil { + return err + } + debugMode := evm.ChainConfig().DebugMode() + txRunMode := c.txProcessor.RunMode() + emitEvent := func() error { + return con.UpdateProgramCache(c, evm, c.caller, codehash, cached) + } + return programs.SetProgramCached( + emitEvent, evm.StateDB, codehash, cached, evm.Context.Time, params, txRunMode, debugMode, + ) +} + +func (con ArbWasmCache) hasAccess(c ctx) bool { + manager, err := c.State.Programs().CacheManagers().IsMember(c.caller) + if err != nil { + return false + } + if manager { + return true + } + owner, err := c.State.ChainOwners().IsMember(c.caller) + return owner && err == nil +} diff --git a/precompiles/context.go b/precompiles/context.go index 08eb0569f..670ffa744 100644 --- a/precompiles/context.go +++ b/precompiles/context.go @@ -37,8 +37,7 @@ type Context struct { func (c *Context) Burn(amount uint64) error { if c.gasLeft < amount { - c.gasLeft = 0 - return vm.ErrOutOfGas + return c.BurnOut() } c.gasLeft -= amount return nil @@ -49,6 +48,15 @@ func (c *Context) Burned() uint64 { return c.gasSupplied - c.gasLeft } +func (c *Context) BurnOut() error { + c.gasLeft = 0 + return vm.ErrOutOfGas +} + +func (c *Context) GasLeft() *uint64 { + return &c.gasLeft +} + func (c *Context) Restrict(err error) { log.Crit("A metered burner was used for access-controlled work", "error", err) } @@ -65,6 +73,10 @@ func (c *Context) TracingInfo() *util.TracingInfo { return c.tracingInfo } +func (c *Context) GetCodeHash(address common.Address) (common.Hash, error) { + return c.State.BackingStorage().GetCodeHash(address) +} + func testContext(caller addr, evm mech) *Context { tracingInfo := util.NewTracingInfo(evm, common.Address{}, types.ArbosAddress, util.TracingDuringEVM) ctx := &Context{ diff --git a/precompiles/precompile.go b/precompiles/precompile.go index 0627ef4c7..c39f2bcb6 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -1,4 +1,4 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE package precompiles @@ -16,8 +16,9 @@ import ( "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbos/util" - templates "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + pgen "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" "github.com/ethereum/go-ethereum/accounts/abi" @@ -514,12 +515,6 @@ func MakePrecompile(metadata *bind.MetaData, implementer interface{}) (addr, *Pr } func Precompiles() map[addr]ArbosPrecompile { - - //nolint:gocritic - hex := func(s string) addr { - return common.HexToAddress(s) - } - contracts := make(map[addr]ArbosPrecompile) insert := func(address addr, impl ArbosPrecompile) *Precompile { @@ -527,12 +522,12 @@ func Precompiles() map[addr]ArbosPrecompile { return impl.Precompile() } - insert(MakePrecompile(templates.ArbInfoMetaData, &ArbInfo{Address: hex("65")})) - insert(MakePrecompile(templates.ArbAddressTableMetaData, &ArbAddressTable{Address: hex("66")})) - insert(MakePrecompile(templates.ArbBLSMetaData, &ArbBLS{Address: hex("67")})) - insert(MakePrecompile(templates.ArbFunctionTableMetaData, &ArbFunctionTable{Address: hex("68")})) - insert(MakePrecompile(templates.ArbosTestMetaData, &ArbosTest{Address: hex("69")})) - ArbGasInfo := insert(MakePrecompile(templates.ArbGasInfoMetaData, &ArbGasInfo{Address: hex("6c")})) + insert(MakePrecompile(pgen.ArbInfoMetaData, &ArbInfo{Address: types.ArbInfoAddress})) + insert(MakePrecompile(pgen.ArbAddressTableMetaData, &ArbAddressTable{Address: types.ArbAddressTableAddress})) + insert(MakePrecompile(pgen.ArbBLSMetaData, &ArbBLS{Address: types.ArbBLSAddress})) + insert(MakePrecompile(pgen.ArbFunctionTableMetaData, &ArbFunctionTable{Address: types.ArbFunctionTableAddress})) + insert(MakePrecompile(pgen.ArbosTestMetaData, &ArbosTest{Address: types.ArbosTestAddress})) + ArbGasInfo := insert(MakePrecompile(pgen.ArbGasInfoMetaData, &ArbGasInfo{Address: types.ArbGasInfoAddress})) ArbGasInfo.methodsByName["GetL1FeesAvailable"].arbosVersion = 10 ArbGasInfo.methodsByName["GetL1RewardRate"].arbosVersion = 11 ArbGasInfo.methodsByName["GetL1RewardRecipient"].arbosVersion = 11 @@ -541,8 +536,8 @@ func Precompiles() map[addr]ArbosPrecompile { ArbGasInfo.methodsByName["GetL1PricingFundsDueForRewards"].arbosVersion = 20 ArbGasInfo.methodsByName["GetL1PricingUnitsSinceUpdate"].arbosVersion = 20 ArbGasInfo.methodsByName["GetLastL1PricingSurplus"].arbosVersion = 20 - insert(MakePrecompile(templates.ArbAggregatorMetaData, &ArbAggregator{Address: hex("6d")})) - insert(MakePrecompile(templates.ArbStatisticsMetaData, &ArbStatistics{Address: hex("6f")})) + insert(MakePrecompile(pgen.ArbAggregatorMetaData, &ArbAggregator{Address: types.ArbAggregatorAddress})) + insert(MakePrecompile(pgen.ArbStatisticsMetaData, &ArbStatistics{Address: types.ArbStatisticsAddress})) eventCtx := func(gasLimit uint64, err error) *Context { if err != nil { @@ -554,14 +549,35 @@ func Precompiles() map[addr]ArbosPrecompile { } } - ArbOwnerPublic := insert(MakePrecompile(templates.ArbOwnerPublicMetaData, &ArbOwnerPublic{Address: hex("6b")})) + ArbOwnerPublicImpl := &ArbOwnerPublic{Address: types.ArbOwnerPublicAddress} + ArbOwnerPublic := insert(MakePrecompile(pgen.ArbOwnerPublicMetaData, ArbOwnerPublicImpl)) ArbOwnerPublic.methodsByName["GetInfraFeeAccount"].arbosVersion = 5 ArbOwnerPublic.methodsByName["RectifyChainOwner"].arbosVersion = 11 ArbOwnerPublic.methodsByName["GetBrotliCompressionLevel"].arbosVersion = 20 ArbOwnerPublic.methodsByName["GetScheduledUpgrade"].arbosVersion = 20 + ArbWasmImpl := &ArbWasm{Address: types.ArbWasmAddress} + ArbWasm := insert(MakePrecompile(pgen.ArbWasmMetaData, ArbWasmImpl)) + ArbWasm.arbosVersion = params.ArbosVersion_Stylus + programs.ProgramNotWasmError = ArbWasmImpl.ProgramNotWasmError + programs.ProgramNotActivatedError = ArbWasmImpl.ProgramNotActivatedError + programs.ProgramNeedsUpgradeError = ArbWasmImpl.ProgramNeedsUpgradeError + programs.ProgramExpiredError = ArbWasmImpl.ProgramExpiredError + programs.ProgramUpToDateError = ArbWasmImpl.ProgramUpToDateError + programs.ProgramKeepaliveTooSoon = ArbWasmImpl.ProgramKeepaliveTooSoonError + for _, method := range ArbWasm.methods { + method.arbosVersion = ArbWasm.arbosVersion + } + + ArbWasmCacheImpl := &ArbWasmCache{Address: types.ArbWasmCacheAddress} + ArbWasmCache := insert(MakePrecompile(pgen.ArbWasmCacheMetaData, ArbWasmCacheImpl)) + ArbWasmCache.arbosVersion = params.ArbosVersion_Stylus + for _, method := range ArbWasmCache.methods { + method.arbosVersion = ArbWasmCache.arbosVersion + } + ArbRetryableImpl := &ArbRetryableTx{Address: types.ArbRetryableTxAddress} - ArbRetryable := insert(MakePrecompile(templates.ArbRetryableTxMetaData, ArbRetryableImpl)) + ArbRetryable := insert(MakePrecompile(pgen.ArbRetryableTxMetaData, ArbRetryableImpl)) arbos.ArbRetryableTxAddress = ArbRetryable.address arbos.RedeemScheduledEventID = ArbRetryable.events["RedeemScheduled"].template.ID arbos.EmitReedeemScheduledEvent = func( @@ -579,30 +595,46 @@ func Precompiles() map[addr]ArbosPrecompile { return ArbRetryableImpl.TicketCreated(context, evm, ticketId) } - ArbSys := insert(MakePrecompile(templates.ArbSysMetaData, &ArbSys{Address: types.ArbSysAddress})) + ArbSys := insert(MakePrecompile(pgen.ArbSysMetaData, &ArbSys{Address: types.ArbSysAddress})) arbos.ArbSysAddress = ArbSys.address arbos.L2ToL1TransactionEventID = ArbSys.events["L2ToL1Transaction"].template.ID arbos.L2ToL1TxEventID = ArbSys.events["L2ToL1Tx"].template.ID - ArbOwnerImpl := &ArbOwner{Address: hex("70")} + ArbOwnerImpl := &ArbOwner{Address: types.ArbOwnerAddress} emitOwnerActs := func(evm mech, method bytes4, owner addr, data []byte) error { context := eventCtx(ArbOwnerImpl.OwnerActsGasCost(method, owner, data)) return ArbOwnerImpl.OwnerActs(context, evm, method, owner, data) } - _, ArbOwner := MakePrecompile(templates.ArbOwnerMetaData, ArbOwnerImpl) + _, ArbOwner := MakePrecompile(pgen.ArbOwnerMetaData, ArbOwnerImpl) ArbOwner.methodsByName["GetInfraFeeAccount"].arbosVersion = 5 ArbOwner.methodsByName["SetInfraFeeAccount"].arbosVersion = 5 ArbOwner.methodsByName["ReleaseL1PricerSurplusFunds"].arbosVersion = 10 ArbOwner.methodsByName["SetChainConfig"].arbosVersion = 11 ArbOwner.methodsByName["SetBrotliCompressionLevel"].arbosVersion = 20 + stylusMethods := []string{ + "SetInkPrice", "SetWasmMaxStackDepth", "SetWasmFreePages", "SetWasmPageGas", + "SetWasmPageLimit", "SetWasmMinInitGas", "SetWasmInitCostScalar", + "SetWasmExpiryDays", "SetWasmKeepaliveDays", + "SetWasmBlockCacheSize", "AddWasmCacheManager", "RemoveWasmCacheManager", + } + for _, method := range stylusMethods { + ArbOwner.methodsByName[method].arbosVersion = params.ArbosVersion_Stylus + } insert(ownerOnly(ArbOwnerImpl.Address, ArbOwner, emitOwnerActs)) - insert(debugOnly(MakePrecompile(templates.ArbDebugMetaData, &ArbDebug{Address: hex("ff")}))) + _, arbDebug := MakePrecompile(pgen.ArbDebugMetaData, &ArbDebug{Address: types.ArbDebugAddress}) + arbDebug.methodsByName["Panic"].arbosVersion = params.ArbosVersion_Stylus + insert(debugOnly(arbDebug.address, arbDebug)) - ArbosActs := insert(MakePrecompile(templates.ArbosActsMetaData, &ArbosActs{Address: types.ArbosAddress})) + ArbosActs := insert(MakePrecompile(pgen.ArbosActsMetaData, &ArbosActs{Address: types.ArbosAddress})) arbos.InternalTxStartBlockMethodID = ArbosActs.GetMethodID("StartBlock") arbos.InternalTxBatchPostingReportMethodID = ArbosActs.GetMethodID("BatchPostingReport") + for _, contract := range contracts { + precompile := contract.Precompile() + arbosState.PrecompileMinArbOSVersions[precompile.address] = precompile.arbosVersion + } + return contracts } @@ -620,6 +652,10 @@ func (p *Precompile) GetMethodID(name string) bytes4 { return *(*bytes4)(method.template.ID) } +func (p *Precompile) ArbosVersion() uint64 { + return p.arbosVersion +} + // Call a precompile in typed form, deserializing its inputs and serializing its outputs func (p *Precompile) Call( input []byte, @@ -745,8 +781,14 @@ func (p *Precompile) Call( } return solErr.data, callerCtx.gasLeft, vm.ErrExecutionReverted } + if errors.Is(errRet, programs.ErrProgramActivation) { + return nil, 0, errRet + } if !errors.Is(errRet, vm.ErrOutOfGas) { - log.Debug("precompile reverted with non-solidity error", "precompile", precompileAddress, "input", input, "err", errRet) + log.Debug( + "precompile reverted with non-solidity error", + "precompile", precompileAddress, "input", input, "err", errRet, + ) } // nolint:errorlint if arbosVersion >= 11 || errRet == vm.ErrExecutionReverted { diff --git a/precompiles/precompile_test.go b/precompiles/precompile_test.go index 975856bce..86047038d 100644 --- a/precompiles/precompile_test.go +++ b/precompiles/precompile_test.go @@ -5,6 +5,7 @@ package precompiles import ( "fmt" + "io" "math/big" "os" "testing" @@ -181,9 +182,10 @@ func TestEventCosts(t *testing.T) { func TestPrecompilesPerArbosVersion(t *testing.T) { // Set up a logger in case log.Crit is called by Precompiles() - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.LvlWarn) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(log.LevelWarn) + log.SetDefault(log.NewLogger(glogger)) expectedNewMethodsPerArbosVersion := map[uint64]int{ 0: 89, @@ -191,13 +193,15 @@ func TestPrecompilesPerArbosVersion(t *testing.T) { 10: 2, 11: 4, 20: 8, + 30: 38, } precompiles := Precompiles() newMethodsPerArbosVersion := make(map[uint64]int) for _, precompile := range precompiles { for _, method := range precompile.Precompile().methods { - newMethodsPerArbosVersion[method.arbosVersion]++ + version := arbmath.MaxInt(method.arbosVersion, precompile.Precompile().arbosVersion) + newMethodsPerArbosVersion[version]++ } } diff --git a/pubsub/common.go b/pubsub/common.go new file mode 100644 index 000000000..9f05304e4 --- /dev/null +++ b/pubsub/common.go @@ -0,0 +1,29 @@ +package pubsub + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" +) + +// CreateStream tries to create stream with given name, if it already exists +// does not return an error. +func CreateStream(ctx context.Context, streamName string, client redis.UniversalClient) error { + _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result() + if err != nil && !StreamExists(ctx, streamName, client) { + return err + } + return nil +} + +// StreamExists returns whether there are any consumer group for specified +// redis stream. +func StreamExists(ctx context.Context, streamName string, client redis.UniversalClient) bool { + got, err := client.Do(ctx, "XINFO", "STREAM", streamName).Result() + if err != nil { + log.Error("Reading redis streams", "error", err) + return false + } + return got != nil +} diff --git a/pubsub/consumer.go b/pubsub/consumer.go new file mode 100644 index 000000000..df3695606 --- /dev/null +++ b/pubsub/consumer.go @@ -0,0 +1,175 @@ +package pubsub + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" + "github.com/google/uuid" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/spf13/pflag" +) + +type ConsumerConfig struct { + // Timeout of result entry in Redis. + ResponseEntryTimeout time.Duration `koanf:"response-entry-timeout"` + // Duration after which consumer is considered to be dead if heartbeat + // is not updated. + KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` +} + +var DefaultConsumerConfig = ConsumerConfig{ + ResponseEntryTimeout: time.Hour, + KeepAliveTimeout: 5 * time.Minute, +} + +var TestConsumerConfig = ConsumerConfig{ + ResponseEntryTimeout: time.Minute, + KeepAliveTimeout: 30 * time.Millisecond, +} + +func ConsumerConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.Duration(prefix+".response-entry-timeout", DefaultConsumerConfig.ResponseEntryTimeout, "timeout for response entry") + f.Duration(prefix+".keepalive-timeout", DefaultConsumerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") +} + +// Consumer implements a consumer for redis stream provides heartbeat to +// indicate it is alive. +type Consumer[Request any, Response any] struct { + stopwaiter.StopWaiter + id string + client redis.UniversalClient + redisStream string + redisGroup string + cfg *ConsumerConfig +} + +type Message[Request any] struct { + ID string + Value Request +} + +func NewConsumer[Request any, Response any](client redis.UniversalClient, streamName string, cfg *ConsumerConfig) (*Consumer[Request, Response], error) { + if streamName == "" { + return nil, fmt.Errorf("redis stream name cannot be empty") + } + return &Consumer[Request, Response]{ + id: uuid.NewString(), + client: client, + redisStream: streamName, + redisGroup: streamName, // There is 1-1 mapping of redis stream and consumer group. + cfg: cfg, + }, nil +} + +// Start starts the consumer to iteratively perform heartbeat in configured intervals. +func (c *Consumer[Request, Response]) Start(ctx context.Context) { + c.StopWaiter.Start(ctx, c) + c.StopWaiter.CallIteratively( + func(ctx context.Context) time.Duration { + c.heartBeat(ctx) + return c.cfg.KeepAliveTimeout / 10 + }, + ) +} + +func (c *Consumer[Request, Response]) StopAndWait() { + c.StopWaiter.StopAndWait() + c.deleteHeartBeat(c.GetParentContext()) +} + +func heartBeatKey(id string) string { + return fmt.Sprintf("consumer:%s:heartbeat", id) +} + +func (c *Consumer[Request, Response]) RedisClient() redis.UniversalClient { + return c.client +} + +func (c *Consumer[Request, Response]) StreamName() string { + return c.redisStream +} + +func (c *Consumer[Request, Response]) heartBeatKey() string { + return heartBeatKey(c.id) +} + +// deleteHeartBeat deletes the heartbeat to indicate it is being shut down. +func (c *Consumer[Request, Response]) deleteHeartBeat(ctx context.Context) { + if err := c.client.Del(ctx, c.heartBeatKey()).Err(); err != nil { + l := log.Info + if ctx.Err() != nil { + l = log.Error + } + l("Deleting heardbeat", "consumer", c.id, "error", err) + } +} + +// heartBeat updates the heartBeat key indicating aliveness. +func (c *Consumer[Request, Response]) heartBeat(ctx context.Context) { + if err := c.client.Set(ctx, c.heartBeatKey(), time.Now().UnixMilli(), 2*c.cfg.KeepAliveTimeout).Err(); err != nil { + l := log.Info + if ctx.Err() != nil { + l = log.Error + } + l("Updating heardbeat", "consumer", c.id, "error", err) + } +} + +// Consumer first checks it there exists pending message that is claimed by +// unresponsive consumer, if not then reads from the stream. +func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Request], error) { + res, err := c.client.XReadGroup(ctx, &redis.XReadGroupArgs{ + Group: c.redisGroup, + Consumer: c.id, + // Receive only messages that were never delivered to any other consumer, + // that is, only new messages. + Streams: []string{c.redisStream, ">"}, + Count: 1, + Block: time.Millisecond, // 0 seems to block the read instead of immediately returning + }).Result() + if errors.Is(err, redis.Nil) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("reading message for consumer: %q: %w", c.id, err) + } + if len(res) != 1 || len(res[0].Messages) != 1 { + return nil, fmt.Errorf("redis returned entries: %+v, for querying single message", res) + } + var ( + value = res[0].Messages[0].Values[messageKey] + data, ok = (value).(string) + ) + if !ok { + return nil, fmt.Errorf("casting request to string: %w", err) + } + var req Request + if err := json.Unmarshal([]byte(data), &req); err != nil { + return nil, fmt.Errorf("unmarshaling value: %v, error: %w", value, err) + } + log.Debug("Redis stream consuming", "consumer_id", c.id, "message_id", res[0].Messages[0].ID) + return &Message[Request]{ + ID: res[0].Messages[0].ID, + Value: req, + }, nil +} + +func (c *Consumer[Request, Response]) SetResult(ctx context.Context, messageID string, result Response) error { + resp, err := json.Marshal(result) + if err != nil { + return fmt.Errorf("marshaling result: %w", err) + } + acquired, err := c.client.SetNX(ctx, messageID, resp, c.cfg.ResponseEntryTimeout).Result() + if err != nil || !acquired { + return fmt.Errorf("setting result for message: %v, error: %w", messageID, err) + } + if _, err := c.client.XAck(ctx, c.redisStream, c.redisGroup, messageID).Result(); err != nil { + return fmt.Errorf("acking message: %v, error: %w", messageID, err) + } + return nil +} diff --git a/pubsub/producer.go b/pubsub/producer.go new file mode 100644 index 000000000..074670ca0 --- /dev/null +++ b/pubsub/producer.go @@ -0,0 +1,299 @@ +// Package pubsub implements publisher/subscriber model (one to many). +// During normal operation, publisher returns "Promise" when publishing a +// message, which will return resposne from consumer when awaited. +// If the consumer processing the request becomes inactive, message is +// re-inserted (if EnableReproduce flag is enabled), and will be picked up by +// another consumer. +// We are assuming here that keeepAliveTimeout is set to some sensible value +// and once consumer becomes inactive, it doesn't activate without restart. +package pubsub + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" + "github.com/google/uuid" + "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/spf13/pflag" +) + +const ( + messageKey = "msg" + defaultGroup = "default_consumer_group" +) + +type Producer[Request any, Response any] struct { + stopwaiter.StopWaiter + id string + client redis.UniversalClient + redisStream string + redisGroup string + cfg *ProducerConfig + + promisesLock sync.RWMutex + promises map[string]*containers.Promise[Response] + + // Used for running checks for pending messages with inactive consumers + // and checking responses from consumers iteratively for the first time when + // Produce is called. + once sync.Once +} + +type ProducerConfig struct { + // When enabled, messages that are sent to consumers that later die before + // processing them, will be re-inserted into the stream to be proceesed by + // another consumer + EnableReproduce bool `koanf:"enable-reproduce"` + // Interval duration in which producer checks for pending messages delivered + // to the consumers that are currently inactive. + CheckPendingInterval time.Duration `koanf:"check-pending-interval"` + // Duration after which consumer is considered to be dead if heartbeat + // is not updated. + KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` + // Interval duration for checking the result set by consumers. + CheckResultInterval time.Duration `koanf:"check-result-interval"` +} + +var DefaultProducerConfig = ProducerConfig{ + EnableReproduce: true, + CheckPendingInterval: time.Second, + KeepAliveTimeout: 5 * time.Minute, + CheckResultInterval: 5 * time.Second, +} + +var TestProducerConfig = ProducerConfig{ + EnableReproduce: true, + CheckPendingInterval: 10 * time.Millisecond, + KeepAliveTimeout: 100 * time.Millisecond, + CheckResultInterval: 5 * time.Millisecond, +} + +func ProducerAddConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".enable-reproduce", DefaultProducerConfig.EnableReproduce, "when enabled, messages with dead consumer will be re-inserted into the stream") + f.Duration(prefix+".check-pending-interval", DefaultProducerConfig.CheckPendingInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") + f.Duration(prefix+".check-result-interval", DefaultProducerConfig.CheckResultInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") + f.Duration(prefix+".keepalive-timeout", DefaultProducerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") +} + +func NewProducer[Request any, Response any](client redis.UniversalClient, streamName string, cfg *ProducerConfig) (*Producer[Request, Response], error) { + if client == nil { + return nil, fmt.Errorf("redis client cannot be nil") + } + if streamName == "" { + return nil, fmt.Errorf("stream name cannot be empty") + } + return &Producer[Request, Response]{ + id: uuid.NewString(), + client: client, + redisStream: streamName, + redisGroup: streamName, // There is 1-1 mapping of redis stream and consumer group. + cfg: cfg, + promises: make(map[string]*containers.Promise[Response]), + }, nil +} + +func (p *Producer[Request, Response]) errorPromisesFor(msgs []*Message[Request]) { + p.promisesLock.Lock() + defer p.promisesLock.Unlock() + for _, msg := range msgs { + if promise, found := p.promises[msg.ID]; found { + promise.ProduceError(fmt.Errorf("internal error, consumer died while serving the request")) + delete(p.promises, msg.ID) + } + } +} + +// checkAndReproduce reproduce pending messages that were sent to consumers +// that are currently inactive. +func (p *Producer[Request, Response]) checkAndReproduce(ctx context.Context) time.Duration { + msgs, err := p.checkPending(ctx) + if err != nil { + log.Error("Checking pending messages", "error", err) + return p.cfg.CheckPendingInterval + } + if len(msgs) == 0 { + return p.cfg.CheckPendingInterval + } + if !p.cfg.EnableReproduce { + p.errorPromisesFor(msgs) + return p.cfg.CheckPendingInterval + } + acked := make(map[string]Request) + for _, msg := range msgs { + if _, err := p.client.XAck(ctx, p.redisStream, p.redisGroup, msg.ID).Result(); err != nil { + log.Error("ACKing message", "error", err) + continue + } + acked[msg.ID] = msg.Value + } + for k, v := range acked { + // Only re-insert messages that were removed the the pending list first. + _, err := p.reproduce(ctx, v, k) + if err != nil { + log.Error("Re-inserting pending messages with inactive consumers", "error", err) + } + } + return p.cfg.CheckPendingInterval +} + +// checkResponses checks iteratively whether response for the promise is ready. +func (p *Producer[Request, Response]) checkResponses(ctx context.Context) time.Duration { + p.promisesLock.Lock() + defer p.promisesLock.Unlock() + for id, promise := range p.promises { + res, err := p.client.Get(ctx, id).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + continue + } + log.Error("Error reading value in redis", "key", id, "error", err) + } + var resp Response + if err := json.Unmarshal([]byte(res), &resp); err != nil { + log.Error("Error unmarshaling", "value", res, "error", err) + continue + } + promise.Produce(resp) + delete(p.promises, id) + } + return p.cfg.CheckResultInterval +} + +func (p *Producer[Request, Response]) Start(ctx context.Context) { + p.StopWaiter.Start(ctx, p) +} + +func (p *Producer[Request, Response]) promisesLen() int { + p.promisesLock.Lock() + defer p.promisesLock.Unlock() + return len(p.promises) +} + +// reproduce is used when Producer claims ownership on the pending +// message that was sent to inactive consumer and reinserts it into the stream, +// so that seamlessly return the answer in the same promise. +func (p *Producer[Request, Response]) reproduce(ctx context.Context, value Request, oldKey string) (*containers.Promise[Response], error) { + val, err := json.Marshal(value) + if err != nil { + return nil, fmt.Errorf("marshaling value: %w", err) + } + id, err := p.client.XAdd(ctx, &redis.XAddArgs{ + Stream: p.redisStream, + Values: map[string]any{messageKey: val}, + }).Result() + if err != nil { + return nil, fmt.Errorf("adding values to redis: %w", err) + } + p.promisesLock.Lock() + defer p.promisesLock.Unlock() + promise := p.promises[oldKey] + if oldKey != "" && promise == nil { + // This will happen if the old consumer became inactive but then ack_d + // the message afterwards. + return nil, fmt.Errorf("error reproducing the message, could not find existing one") + } + if oldKey == "" || promise == nil { + pr := containers.NewPromise[Response](nil) + promise = &pr + } + delete(p.promises, oldKey) + p.promises[id] = promise + return promise, nil +} + +func (p *Producer[Request, Response]) Produce(ctx context.Context, value Request) (*containers.Promise[Response], error) { + log.Debug("Redis stream producing", "value", value) + p.once.Do(func() { + p.StopWaiter.CallIteratively(p.checkAndReproduce) + p.StopWaiter.CallIteratively(p.checkResponses) + }) + return p.reproduce(ctx, value, "") +} + +// Check if a consumer is with specified ID is alive. +func (p *Producer[Request, Response]) isConsumerAlive(ctx context.Context, consumerID string) bool { + if _, err := p.client.Get(ctx, heartBeatKey(consumerID)).Int64(); err != nil { + return false + } + return true +} + +func (p *Producer[Request, Response]) havePromiseFor(messageID string) bool { + p.promisesLock.Lock() + defer p.promisesLock.Unlock() + _, found := p.promises[messageID] + return found +} + +func (p *Producer[Request, Response]) checkPending(ctx context.Context) ([]*Message[Request], error) { + pendingMessages, err := p.client.XPendingExt(ctx, &redis.XPendingExtArgs{ + Stream: p.redisStream, + Group: p.redisGroup, + Start: "-", + End: "+", + Count: 100, + }).Result() + + if err != nil && !errors.Is(err, redis.Nil) { + return nil, fmt.Errorf("querying pending messages: %w", err) + } + if len(pendingMessages) == 0 { + return nil, nil + } + // IDs of the pending messages with inactive consumers. + var ids []string + active := make(map[string]bool) + for _, msg := range pendingMessages { + // Ignore messages not produced by this producer. + if !p.havePromiseFor(msg.ID) { + continue + } + alive, found := active[msg.Consumer] + if !found { + alive = p.isConsumerAlive(ctx, msg.Consumer) + active[msg.Consumer] = alive + } + if alive { + continue + } + ids = append(ids, msg.ID) + } + if len(ids) == 0 { + log.Trace("There are no pending messages with inactive consumers") + return nil, nil + } + log.Info("Attempting to claim", "messages", ids) + claimedMsgs, err := p.client.XClaim(ctx, &redis.XClaimArgs{ + Stream: p.redisStream, + Group: p.redisGroup, + Consumer: p.id, + MinIdle: p.cfg.KeepAliveTimeout, + Messages: ids, + }).Result() + if err != nil { + return nil, fmt.Errorf("claiming ownership on messages: %v, error: %w", ids, err) + } + var res []*Message[Request] + for _, msg := range claimedMsgs { + data, ok := (msg.Values[messageKey]).(string) + if !ok { + return nil, fmt.Errorf("casting request: %v to bytes", msg.Values[messageKey]) + } + var req Request + if err := json.Unmarshal([]byte(data), &req); err != nil { + return nil, fmt.Errorf("marshaling value: %v, error: %w", msg.Values[messageKey], err) + } + res = append(res, &Message[Request]{ + ID: msg.ID, + Value: req, + }) + } + return res, nil +} diff --git a/pubsub/pubsub_test.go b/pubsub/pubsub_test.go new file mode 100644 index 000000000..72504602e --- /dev/null +++ b/pubsub/pubsub_test.go @@ -0,0 +1,336 @@ +package pubsub + +import ( + "context" + "errors" + "fmt" + "os" + "sort" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/redisutil" +) + +var ( + consumersCount = 10 + messagesCount = 100 +) + +type testRequest struct { + Request string +} + +type testResponse struct { + Response string +} + +func createRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { + t.Helper() + // Stream name and group name are the same. + if _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result(); err != nil { + t.Fatalf("Error creating stream group: %v", err) + } +} + +func destroyRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { + t.Helper() + if _, err := client.XGroupDestroy(ctx, streamName, streamName).Result(); err != nil { + log.Debug("Error destroying a stream group", "error", err) + } +} + +type configOpt interface { + apply(consCfg *ConsumerConfig, prodCfg *ProducerConfig) +} + +type disableReproduce struct{} + +func (e *disableReproduce) apply(_ *ConsumerConfig, prodCfg *ProducerConfig) { + prodCfg.EnableReproduce = false +} + +func producerCfg() *ProducerConfig { + return &ProducerConfig{ + EnableReproduce: TestProducerConfig.EnableReproduce, + CheckPendingInterval: TestProducerConfig.CheckPendingInterval, + KeepAliveTimeout: TestProducerConfig.KeepAliveTimeout, + CheckResultInterval: TestProducerConfig.CheckResultInterval, + } +} + +func consumerCfg() *ConsumerConfig { + return &ConsumerConfig{ + ResponseEntryTimeout: TestConsumerConfig.ResponseEntryTimeout, + KeepAliveTimeout: TestConsumerConfig.KeepAliveTimeout, + } +} + +func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) (*Producer[testRequest, testResponse], []*Consumer[testRequest, testResponse]) { + t.Helper() + redisClient, err := redisutil.RedisClientFromURL(redisutil.CreateTestRedis(ctx, t)) + if err != nil { + t.Fatalf("RedisClientFromURL() unexpected error: %v", err) + } + prodCfg, consCfg := producerCfg(), consumerCfg() + streamName := fmt.Sprintf("stream:%s", uuid.NewString()) + for _, o := range opts { + o.apply(consCfg, prodCfg) + } + producer, err := NewProducer[testRequest, testResponse](redisClient, streamName, prodCfg) + if err != nil { + t.Fatalf("Error creating new producer: %v", err) + } + + var consumers []*Consumer[testRequest, testResponse] + for i := 0; i < consumersCount; i++ { + c, err := NewConsumer[testRequest, testResponse](redisClient, streamName, consCfg) + if err != nil { + t.Fatalf("Error creating new consumer: %v", err) + } + consumers = append(consumers, c) + } + createRedisGroup(ctx, t, streamName, producer.client) + t.Cleanup(func() { + ctx := context.Background() + destroyRedisGroup(ctx, t, streamName, producer.client) + var keys []string + for _, c := range consumers { + keys = append(keys, c.heartBeatKey()) + } + if _, err := producer.client.Del(ctx, keys...).Result(); err != nil { + log.Debug("Error deleting heartbeat keys", "error", err) + } + }) + return producer, consumers +} + +func messagesMaps(n int) []map[string]string { + ret := make([]map[string]string, n) + for i := 0; i < n; i++ { + ret[i] = make(map[string]string) + } + return ret +} + +func wantMessages(n int) []string { + var ret []string + for i := 0; i < n; i++ { + ret = append(ret, fmt.Sprintf("msg: %d", i)) + } + sort.Strings(ret) + return ret +} + +func flatten(responses [][]string) []string { + var ret []string + for _, v := range responses { + ret = append(ret, v...) + } + sort.Strings(ret) + return ret +} + +func produceMessages(ctx context.Context, msgs []string, producer *Producer[testRequest, testResponse]) ([]*containers.Promise[testResponse], error) { + var promises []*containers.Promise[testResponse] + for i := 0; i < messagesCount; i++ { + promise, err := producer.Produce(ctx, testRequest{Request: msgs[i]}) + if err != nil { + return nil, err + } + promises = append(promises, promise) + } + return promises, nil +} + +func awaitResponses(ctx context.Context, promises []*containers.Promise[testResponse]) ([]string, error) { + var ( + responses []string + errs []error + ) + for _, p := range promises { + res, err := p.Await(ctx) + if err != nil { + errs = append(errs, err) + continue + } + responses = append(responses, res.Response) + } + return responses, errors.Join(errs...) +} + +// consume messages from every consumer except stopped ones. +func consume(ctx context.Context, t *testing.T, consumers []*Consumer[testRequest, testResponse]) ([]map[string]string, [][]string) { + t.Helper() + gotMessages := messagesMaps(consumersCount) + wantResponses := make([][]string, consumersCount) + for idx := 0; idx < consumersCount; idx++ { + if consumers[idx].Stopped() { + continue + } + idx, c := idx, consumers[idx] + c.Start(ctx) + c.StopWaiter.LaunchThread( + func(ctx context.Context) { + for { + + res, err := c.Consume(ctx) + if err != nil { + if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { + t.Errorf("Consume() unexpected error: %v", err) + continue + } + return + } + if res == nil { + continue + } + gotMessages[idx][res.ID] = res.Value.Request + resp := fmt.Sprintf("result for: %v", res.ID) + if err := c.SetResult(ctx, res.ID, testResponse{Response: resp}); err != nil { + t.Errorf("Error setting a result: %v", err) + } + wantResponses[idx] = append(wantResponses[idx], resp) + } + }) + } + return gotMessages, wantResponses +} + +func TestRedisProduce(t *testing.T) { + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + t.Parallel() + for _, tc := range []struct { + name string + killConsumers bool + }{ + { + name: "all consumers are active", + killConsumers: false, + }, + { + name: "some consumers killed, others should take over their work", + killConsumers: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + producer, consumers := newProducerConsumers(ctx, t) + producer.Start(ctx) + wantMsgs := wantMessages(messagesCount) + promises, err := produceMessages(ctx, wantMsgs, producer) + if err != nil { + t.Fatalf("Error producing messages: %v", err) + } + if tc.killConsumers { + // Consumer messages in every third consumer but don't ack them to check + // that other consumers will claim ownership on those messages. + for i := 0; i < len(consumers); i += 3 { + consumers[i].Start(ctx) + if _, err := consumers[i].Consume(ctx); err != nil { + t.Errorf("Error consuming message: %v", err) + } + consumers[i].StopAndWait() + } + + } + time.Sleep(time.Second) + gotMessages, wantResponses := consume(ctx, t, consumers) + gotResponses, err := awaitResponses(ctx, promises) + if err != nil { + t.Fatalf("Error awaiting responses: %v", err) + } + producer.StopAndWait() + for _, c := range consumers { + c.StopAndWait() + } + got, err := mergeValues(gotMessages) + if err != nil { + t.Fatalf("mergeMaps() unexpected error: %v", err) + } + + if diff := cmp.Diff(wantMsgs, got); diff != "" { + t.Errorf("Unexpected diff (-want +got):\n%s\n", diff) + } + wantResp := flatten(wantResponses) + sort.Strings(gotResponses) + if diff := cmp.Diff(wantResp, gotResponses); diff != "" { + t.Errorf("Unexpected diff in responses:\n%s\n", diff) + } + if cnt := producer.promisesLen(); cnt != 0 { + t.Errorf("Producer still has %d unfullfilled promises", cnt) + } + }) + } +} + +func TestRedisReproduceDisabled(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + producer, consumers := newProducerConsumers(ctx, t, &disableReproduce{}) + producer.Start(ctx) + wantMsgs := wantMessages(messagesCount) + promises, err := produceMessages(ctx, wantMsgs, producer) + if err != nil { + t.Fatalf("Error producing messages: %v", err) + } + + // Consumer messages in every third consumer but don't ack them to check + // that other consumers will claim ownership on those messages. + for i := 0; i < len(consumers); i += 3 { + consumers[i].Start(ctx) + if _, err := consumers[i].Consume(ctx); err != nil { + t.Errorf("Error consuming message: %v", err) + } + consumers[i].StopAndWait() + } + + gotMessages, _ := consume(ctx, t, consumers) + gotResponses, err := awaitResponses(ctx, promises) + if err == nil { + t.Fatalf("All promises were fullfilled with reproduce disabled and some consumers killed") + } + producer.StopAndWait() + for _, c := range consumers { + c.StopWaiter.StopAndWait() + } + got, err := mergeValues(gotMessages) + if err != nil { + t.Fatalf("mergeMaps() unexpected error: %v", err) + } + wantMsgCnt := messagesCount - ((consumersCount + 2) / 3) + if len(got) != wantMsgCnt { + t.Fatalf("Got: %d messages, want %d", len(got), wantMsgCnt) + } + if len(gotResponses) != wantMsgCnt { + t.Errorf("Got %d responses want: %d\n", len(gotResponses), wantMsgCnt) + } + if cnt := producer.promisesLen(); cnt != 0 { + t.Errorf("Producer still has %d unfullfilled promises", cnt) + } +} + +// mergeValues merges maps from the slice and returns their values. +// Returns and error if there exists duplicate key. +func mergeValues(messages []map[string]string) ([]string, error) { + res := make(map[string]any) + var ret []string + for _, m := range messages { + for k, v := range m { + if _, found := res[k]; found { + return nil, fmt.Errorf("duplicate key: %v", k) + } + res[k] = v + ret = append(ret, v) + } + } + sort.Strings(ret) + return ret, nil +} diff --git a/relay/relay.go b/relay/relay.go index 8e2997138..89bb899f2 100644 --- a/relay/relay.go +++ b/relay/relay.go @@ -10,8 +10,6 @@ import ( flag "github.com/spf13/pflag" - "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/broadcastclient" "github.com/offchainlabs/nitro/broadcastclients" @@ -120,7 +118,7 @@ func (r *Relay) StopAndWait() { type Config struct { Conf genericconf.ConfConfig `koanf:"conf"` Chain L2Config `koanf:"chain"` - LogLevel int `koanf:"log-level"` + LogLevel string `koanf:"log-level"` LogType string `koanf:"log-type"` Metrics bool `koanf:"metrics"` MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` @@ -133,7 +131,7 @@ type Config struct { var ConfigDefault = Config{ Conf: genericconf.ConfConfigDefault, Chain: L2ConfigDefault, - LogLevel: int(log.LvlInfo), + LogLevel: "INFO", LogType: "plaintext", Metrics: false, MetricsServer: genericconf.MetricsServerConfigDefault, @@ -146,7 +144,7 @@ var ConfigDefault = Config{ func ConfigAddOptions(f *flag.FlagSet) { genericconf.ConfConfigAddOptions("conf", f) L2ConfigAddOptions("chain", f) - f.Int("log-level", ConfigDefault.LogLevel, "log level") + f.String("log-level", ConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") f.String("log-type", ConfigDefault.LogType, "log type") f.Bool("metrics", ConfigDefault.Metrics, "enable metrics") genericconf.MetricsServerAddOptions("metrics-server", f) diff --git a/scripts/split-val-entry.sh b/scripts/split-val-entry.sh new file mode 100755 index 000000000..8e1be0f6c --- /dev/null +++ b/scripts/split-val-entry.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +xxd -l 32 -ps -c 40 /dev/urandom > /tmp/nitro-val.jwt + +echo launching validation servers +# To add validation server: +# > launch them here with a different port and --validation.wasm.root-path +# add their port to wait loop +# edit validation-server-configs-list to include the other nodes +/usr/local/bin/nitro-val --file-logging.enable=false --auth.addr 127.0.0.10 --auth.origins 127.0.0.1 --auth.jwtsecret /tmp/nitro-val.jwt --auth.port 52000 & +/home/user/nitro-legacy/bin/nitro-val --file-logging.enable=false --auth.addr 127.0.0.10 --auth.origins 127.0.0.1 --auth.jwtsecret /tmp/nitro-val.jwt --auth.port 52001 --validation.wasm.root-path /home/user/nitro-legacy/machines & +for port in 52000 52001; do + while ! nc -w1 -z 127.0.0.10 $port; do + echo waiting for validation port $port + sleep 1 + done +done +echo launching nitro-node +/usr/local/bin/nitro --validation.wasm.allowed-wasm-module-roots /home/user/nitro-legacy/machines,/home/user/target/machines --node.block-validator.validation-server-configs-list='[{"jwtsecret":"/tmp/nitro-val.jwt","url":"http://127.0.0.10:52000"}, {"jwtsecret":"/tmp/nitro-val.jwt","url":"http://127.0.0.10:52001"}]' "$@" diff --git a/solgen/gen.go b/solgen/gen.go index 9c08e167c..eee004a96 100644 --- a/solgen/gen.go +++ b/solgen/gen.go @@ -68,7 +68,7 @@ func main() { } root := filepath.Dir(filename) parent := filepath.Dir(root) - filePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "build", "contracts", "src", "*", "*", "*.json")) + filePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "build", "contracts", "src", "*", "*.sol", "*.json")) if err != nil { log.Fatal(err) } @@ -105,7 +105,7 @@ func main() { modInfo.addArtifact(artifact) } - yulFilePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "out", "yul", "*", "*.json")) + yulFilePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "out", "*", "*.yul", "*.json")) if err != nil { log.Fatal(err) } diff --git a/staker/block_validator.go b/staker/block_validator.go index 56cd5307d..0fea05469 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -8,14 +8,13 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "runtime" "sync" "sync/atomic" "testing" "time" - flag "github.com/spf13/pflag" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -27,6 +26,8 @@ import ( "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/client/redis" + "github.com/spf13/pflag" ) var ( @@ -74,6 +75,13 @@ type BlockValidator struct { sendRecordChan chan struct{} progressValidationsChan chan struct{} + chosenValidator map[common.Hash]validator.ValidationSpawner + + // wasmModuleRoot + moduleMutex sync.Mutex + currentWasmModuleRoot common.Hash + pendingWasmModuleRoot common.Hash + // for testing only testingProgressMadeChan chan struct{} @@ -84,8 +92,9 @@ type BlockValidator struct { type BlockValidatorConfig struct { Enable bool `koanf:"enable"` + RedisValidationClientConfig redis.ValidationClientConfig `koanf:"redis-validation-client-config"` ValidationServer rpcclient.ClientConfig `koanf:"validation-server" reload:"hot"` - ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs" reload:"hot"` + ValidationServerConfigs []rpcclient.ClientConfig `koanf:"validation-server-configs"` ValidationPoll time.Duration `koanf:"validation-poll" reload:"hot"` PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` @@ -94,7 +103,7 @@ type BlockValidatorConfig struct { FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` - ValidationServerConfigsList string `koanf:"validation-server-configs-list" reload:"hot"` + ValidationServerConfigsList string `koanf:"validation-server-configs-list"` memoryFreeLimit int } @@ -109,23 +118,20 @@ func (c *BlockValidatorConfig) Validate() error { } c.memoryFreeLimit = limit } - if c.ValidationServerConfigs == nil { - if c.ValidationServerConfigsList == "default" { - c.ValidationServerConfigs = []rpcclient.ClientConfig{c.ValidationServer} - } else { - var validationServersConfigs []rpcclient.ClientConfig - if err := json.Unmarshal([]byte(c.ValidationServerConfigsList), &validationServersConfigs); err != nil { + streamsEnabled := c.RedisValidationClientConfig.Enabled() + if len(c.ValidationServerConfigs) == 0 { + c.ValidationServerConfigs = []rpcclient.ClientConfig{c.ValidationServer} + if c.ValidationServerConfigsList != "default" { + var executionServersConfigs []rpcclient.ClientConfig + if err := json.Unmarshal([]byte(c.ValidationServerConfigsList), &executionServersConfigs); err != nil && !streamsEnabled { return fmt.Errorf("failed to parse block-validator validation-server-configs-list string: %w", err) } - c.ValidationServerConfigs = validationServersConfigs + c.ValidationServerConfigs = executionServersConfigs } } - if len(c.ValidationServerConfigs) == 0 { - return fmt.Errorf("block-validator validation-server-configs is empty, need at least one validation server config") - } - for _, serverConfig := range c.ValidationServerConfigs { - if err := serverConfig.Validate(); err != nil { - return fmt.Errorf("failed to validate one of the block-validator validation-server-configs. url: %s, err: %w", serverConfig.URL, err) + for i := range c.ValidationServerConfigs { + if err := c.ValidationServerConfigs[i].Validate(); err != nil { + return fmt.Errorf("failed to validate one of the block-validator validation-server-configs. url: %s, err: %w", c.ValidationServerConfigs[i].URL, err) } } return nil @@ -137,10 +143,11 @@ type BlockValidatorDangerousConfig struct { type BlockValidatorConfigFetcher func() *BlockValidatorConfig -func BlockValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { +func BlockValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBlockValidatorConfig.Enable, "enable block-by-block validation") rpcclient.RPCClientAddOptions(prefix+".validation-server", f, &DefaultBlockValidatorConfig.ValidationServer) - f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of validation rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") + redis.ValidationClientConfigAddOptions(prefix+".redis-validation-client-config", f) + f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of execution rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") f.Duration(prefix+".validation-poll", DefaultBlockValidatorConfig.ValidationPoll, "poll time to check validations") f.Uint64(prefix+".forward-blocks", DefaultBlockValidatorConfig.ForwardBlocks, "prepare entries for up to that many blocks ahead of validation (small footprint)") f.Uint64(prefix+".prerecorded-blocks", DefaultBlockValidatorConfig.PrerecordedBlocks, "record that many blocks ahead of validation (larger footprint)") @@ -151,7 +158,7 @@ func BlockValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".memory-free-limit", DefaultBlockValidatorConfig.MemoryFreeLimit, "minimum free-memory limit after reaching which the blockvalidator pauses validation. Enabled by default as 1GB, to disable provide empty string") } -func BlockValidatorDangerousConfigAddOptions(prefix string, f *flag.FlagSet) { +func BlockValidatorDangerousConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".reset-block-validation", DefaultBlockValidatorDangerousConfig.ResetBlockValidation, "resets block-by-block validation, starting again at genesis") } @@ -159,6 +166,7 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ Enable: false, ValidationServerConfigsList: "default", ValidationServer: rpcclient.DefaultClientConfig, + RedisValidationClientConfig: redis.DefaultValidationClientConfig, ValidationPoll: time.Second, ForwardBlocks: 1024, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), @@ -170,17 +178,18 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ } var TestBlockValidatorConfig = BlockValidatorConfig{ - Enable: false, - ValidationServer: rpcclient.TestClientConfig, - ValidationServerConfigs: []rpcclient.ClientConfig{rpcclient.TestClientConfig}, - ValidationPoll: 100 * time.Millisecond, - ForwardBlocks: 128, - PrerecordedBlocks: uint64(2 * runtime.NumCPU()), - CurrentModuleRoot: "latest", - PendingUpgradeModuleRoot: "latest", - FailureIsFatal: true, - Dangerous: DefaultBlockValidatorDangerousConfig, - MemoryFreeLimit: "default", + Enable: false, + ValidationServer: rpcclient.TestClientConfig, + ValidationServerConfigs: []rpcclient.ClientConfig{rpcclient.TestClientConfig}, + RedisValidationClientConfig: redis.TestValidationClientConfig, + ValidationPoll: 100 * time.Millisecond, + ForwardBlocks: 128, + PrerecordedBlocks: uint64(2 * runtime.NumCPU()), + CurrentModuleRoot: "latest", + PendingUpgradeModuleRoot: "latest", + FailureIsFatal: true, + Dangerous: DefaultBlockValidatorDangerousConfig, + MemoryFreeLimit: "default", } var DefaultBlockValidatorDangerousConfig = BlockValidatorDangerousConfig{ @@ -321,6 +330,17 @@ func nonBlockingTrigger(channel chan struct{}) { } } +func (v *BlockValidator) GetModuleRootsToValidate() []common.Hash { + v.moduleMutex.Lock() + defer v.moduleMutex.Unlock() + + validatingModuleRoots := []common.Hash{v.currentWasmModuleRoot} + if v.currentWasmModuleRoot != v.pendingWasmModuleRoot && v.pendingWasmModuleRoot != (common.Hash{}) { + validatingModuleRoots = append(validatingModuleRoots, v.pendingWasmModuleRoot) + } + return validatingModuleRoots +} + // called from NewBlockValidator, doesn't need to catch locks func ReadLastValidatedInfo(db ethdb.Database) (*GlobalStateValidatedInfo, error) { exists, err := db.Has(lastGlobalStateValidatedInfoKey) @@ -449,8 +469,13 @@ func (v *BlockValidator) writeToFile(validationEntry *validationEntry, moduleRoo if err != nil { return err } - _, err = v.execSpawner.WriteToFile(input, validationEntry.End, moduleRoot).Await(v.GetContext()) - return err + for _, spawner := range v.execSpawners { + if validator.SpawnerSupportsModule(spawner, moduleRoot) { + _, err = spawner.WriteToFile(input, validationEntry.End, moduleRoot).Await(v.GetContext()) + return err + } + } + return errors.New("did not find exec spawner for wasmModuleRoot") } func (v *BlockValidator) SetCurrentWasmModuleRoot(hash common.Hash) error { @@ -549,7 +574,10 @@ func (v *BlockValidator) createNextValidationEntry(ctx context.Context) (bool, e } else { return false, fmt.Errorf("illegal batch msg count %d pos %d batch %d", v.nextCreateBatchMsgCount, pos, endGS.Batch) } - entry, err := newValidationEntry(pos, v.nextCreateStartGS, endGS, msg, v.nextCreateBatch, v.nextCreateBatchBlockHash, v.nextCreatePrevDelayed) + chainConfig := v.streamer.ChainConfig() + entry, err := newValidationEntry( + pos, v.nextCreateStartGS, endGS, msg, v.nextCreateBatch, v.nextCreateBatchBlockHash, v.nextCreatePrevDelayed, chainConfig, + ) if err != nil { return false, err } @@ -693,14 +721,6 @@ func (v *BlockValidator) advanceValidations(ctx context.Context) (*arbutil.Messa defer v.reorgMutex.RUnlock() wasmRoots := v.GetModuleRootsToValidate() - rooms := make([]int, len(v.validationSpawners)) - currentSpawnerIndex := 0 - for i, spawner := range v.validationSpawners { - here := spawner.Room() / len(wasmRoots) - if here > 0 { - rooms[i] = here - } - } pos := v.validated() - 1 // to reverse the first +1 in the loop validationsLoop: for { @@ -769,15 +789,16 @@ validationsLoop: log.Trace("result validated", "count", v.validated(), "blockHash", v.lastValidGS.BlockHash) continue } - for currentSpawnerIndex < len(rooms) { - if rooms[currentSpawnerIndex] > 0 { - break + for _, moduleRoot := range wasmRoots { + if v.chosenValidator[moduleRoot] == nil { + notFoundErr := fmt.Errorf("did not find spawner for moduleRoot :%v", moduleRoot) + v.possiblyFatal(notFoundErr) + return nil, notFoundErr + } + if v.chosenValidator[moduleRoot].Room() == 0 { + log.Trace("advanceValidations: no more room", "moduleRoot", moduleRoot) + return nil, nil } - currentSpawnerIndex++ - } - if currentSpawnerIndex == len(rooms) { - log.Trace("advanceValidations: no more room", "pos", pos) - return nil, nil } if v.isMemoryLimitExceeded() { log.Warn("advanceValidations: aborting due to running low on memory") @@ -797,8 +818,8 @@ validationsLoop: defer validatorPendingValidationsGauge.Dec(1) var runs []validator.ValidationRun for _, moduleRoot := range wasmRoots { - run := v.validationSpawners[currentSpawnerIndex].Launch(input, moduleRoot) - log.Trace("advanceValidations: launched", "pos", validationStatus.Entry.Pos, "moduleRoot", moduleRoot, "spawner", currentSpawnerIndex) + run := v.chosenValidator[moduleRoot].Launch(input, moduleRoot) + log.Trace("advanceValidations: launched", "pos", validationStatus.Entry.Pos, "moduleRoot", moduleRoot) runs = append(runs, run) } validationCtx, cancel := context.WithCancel(ctx) @@ -821,10 +842,6 @@ validationsLoop: } nonBlockingTrigger(v.progressValidationsChan) }) - rooms[currentSpawnerIndex]-- - if rooms[currentSpawnerIndex] == 0 { - currentSpawnerIndex++ - } } } } @@ -1030,10 +1047,11 @@ func (v *BlockValidator) Reorg(ctx context.Context, count arbutil.MessageIndex) // Initialize must be called after SetCurrentWasmModuleRoot sets the current one func (v *BlockValidator) Initialize(ctx context.Context) error { config := v.config() + currentModuleRoot := config.CurrentModuleRoot switch currentModuleRoot { case "latest": - latest, err := v.execSpawner.LatestWasmModuleRoot().Await(ctx) + latest, err := v.GetLatestWasmModuleRoot(ctx) if err != nil { return err } @@ -1048,7 +1066,52 @@ func (v *BlockValidator) Initialize(ctx context.Context) error { return errors.New("current-module-root config value illegal") } } + pendingModuleRoot := config.PendingUpgradeModuleRoot + if pendingModuleRoot != "" { + if pendingModuleRoot == "latest" { + latest, err := v.GetLatestWasmModuleRoot(ctx) + if err != nil { + return err + } + v.pendingWasmModuleRoot = latest + } else { + valid, _ := regexp.MatchString("(0x)?[0-9a-fA-F]{64}", pendingModuleRoot) + v.pendingWasmModuleRoot = common.HexToHash(pendingModuleRoot) + if (!valid || v.pendingWasmModuleRoot == common.Hash{}) { + return errors.New("pending-upgrade-module-root config value illegal") + } + } + } log.Info("BlockValidator initialized", "current", v.currentWasmModuleRoot, "pending", v.pendingWasmModuleRoot) + moduleRoots := []common.Hash{v.currentWasmModuleRoot} + if v.pendingWasmModuleRoot != v.currentWasmModuleRoot && v.pendingWasmModuleRoot != (common.Hash{}) { + moduleRoots = append(moduleRoots, v.pendingWasmModuleRoot) + } + // First spawner is always RedisValidationClient if RedisStreams are enabled. + if v.redisValidator != nil { + err := v.redisValidator.Initialize(ctx, moduleRoots) + if err != nil { + return err + } + } + v.chosenValidator = make(map[common.Hash]validator.ValidationSpawner) + for _, root := range moduleRoots { + if v.redisValidator != nil && validator.SpawnerSupportsModule(v.redisValidator, root) { + v.chosenValidator[root] = v.redisValidator + log.Info("validator chosen", "WasmModuleRoot", root, "chosen", "redis") + } else { + for _, spawner := range v.execSpawners { + if validator.SpawnerSupportsModule(spawner, root) { + v.chosenValidator[root] = spawner + log.Info("validator chosen", "WasmModuleRoot", root, "chosen", spawner.Name()) + break + } + } + if v.chosenValidator[root] == nil { + return fmt.Errorf("cannot validate WasmModuleRoot %v", root) + } + } + } return nil } diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go new file mode 100644 index 000000000..8cca4bb83 --- /dev/null +++ b/staker/challenge-cache/cache.go @@ -0,0 +1,242 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +/* +* Package challengecache stores hashes required for making history commitments in Arbitrum BOLD. +When a challenge begins, validators need to post Merkle commitments to a series of block hashes to +narrow down their disagreement to a single block. Once a disagreement is reached, another BOLD challenge begins +to narrow down within the execution of a block. This requires using the Arbitrator emulator to compute +the intermediate hashes of executing the block as WASM opcodes. These hashes are expensive to compute, so we +store them in a filesystem cache to avoid recomputing them and for hierarchical access. +Each file contains a list of 32 byte hashes, concatenated together as bytes. +Using this structure, we can namespace hashes by message number and by challenge level. + +Once a validator receives a full list of computed machine hashes for the first time from a validatio node, +it will write the hashes to this filesystem hierarchy for fast access next time these hashes are needed. + +Example uses: +- Obtain all the hashes for the execution of message num 70 to 71 for a given wavm module root. +- Obtain all the hashes from step 100 to 101 at subchallenge level 1 for the execution of message num 70. + + wavm-module-root-0xab/ + rollup-block-hash-0x12...-message-num-70/ + hashes.bin + subchallenge-level-1-big-step-100/ + hashes.bin + +We namespace top-level block challenges by wavm module root. Then, we can retrieve +the hashes for any data within a challenge or associated subchallenge based on the hierarchy above. +*/ + +package challengecache + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +var ( + ErrNotFoundInCache = errors.New("no found in challenge cache") + ErrFileAlreadyExists = errors.New("file already exists") + ErrNoHashes = errors.New("no hashes being written") + hashesFileName = "hashes.bin" + wavmModuleRootPrefix = "wavm-module-root" + rollupBlockHashPrefix = "rollup-block-hash" + messageNumberPrefix = "message-num" + bigStepPrefix = "big-step" + challengeLevelPrefix = "subchallenge-level" +) + +// HistoryCommitmentCacher can retrieve history commitment hashes given lookup keys. +type HistoryCommitmentCacher interface { + Get(lookup *Key, numToRead uint64) ([]common.Hash, error) + Put(lookup *Key, hashes []common.Hash) error +} + +// Cache for history commitments on disk. +type Cache struct { + baseDir string +} + +// New cache from a base directory path. +func New(baseDir string) (*Cache, error) { + if _, err := os.Stat(baseDir); err != nil { + if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { + return nil, fmt.Errorf("could not make base cache directory %s: %w", baseDir, err) + } + } + return &Cache{ + baseDir: baseDir, + }, nil +} + +// Key for cache lookups includes the wavm module root of a challenge, as well +// as the heights for messages and big steps as needed. +type Key struct { + RollupBlockHash common.Hash + WavmModuleRoot common.Hash + MessageHeight uint64 + StepHeights []uint64 +} + +// Get a list of hashes from the cache from index 0 up to a certain index. Hashes are saved as files in the directory +// hierarchy for the cache. If a file is not present, ErrNotFoundInCache +// is returned. +func (c *Cache) Get( + lookup *Key, + numToRead uint64, +) ([]common.Hash, error) { + fName, err := determineFilePath(c.baseDir, lookup) + if err != nil { + return nil, err + } + if _, err := os.Stat(fName); err != nil { + log.Warn("Cache miss", "fileName", fName) + return nil, ErrNotFoundInCache + } + log.Debug("Cache hit", "fileName", fName) + f, err := os.Open(fName) + if err != nil { + return nil, err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("Could not close file after reading", "err", err, "file", fName) + } + }() + return readHashes(f, numToRead) +} + +// Put a list of hashes into the cache. +// Hashes are saved as files in a directory hierarchy for the cache. +// This function first creates a temporary file, writes the hashes to it, and then renames the file +// to the final directory to ensure atomic writes. +func (c *Cache) Put(lookup *Key, hashes []common.Hash) error { + // We should error if trying to put 0 hashes to disk. + if len(hashes) == 0 { + return ErrNoHashes + } + fName, err := determineFilePath(c.baseDir, lookup) + if err != nil { + return err + } + // We create a tmp file to write our hashes to first. If writing fails, + // we don't want to leave a half-written file in our cache directory. + // Once writing succeeds, we rename in an atomic operation to the correct file name + // in the cache directory hierarchy. + tmp, err := os.MkdirTemp(c.baseDir, "tmpdir") + if err != nil { + return err + } + tmpFName := filepath.Join(tmp, fName) + dir := filepath.Dir(tmpFName) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("could not make tmp directory %s: %w", dir, err) + } + f, err := os.Create(tmpFName) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("Could not close file after writing", "err", err, "file", fName) + } + }() + if err := writeHashes(f, hashes); err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(fName), os.ModePerm); err != nil { + return fmt.Errorf("could not make file directory %s: %w", fName, err) + } + // If the file writing was successful, we rename the file from the tmp directory + // into our cache directory. This is an atomic operation. + // For more information on this atomic write pattern, see: + // https://stackoverflow.com/questions/2333872/how-to-make-file-creation-an-atomic-operation + return os.Rename(tmpFName /*old */, fName /* new */) +} + +// Reads 32 bytes at a time from a reader up to a specified height. If none, then read all. +func readHashes(r io.Reader, numToRead uint64) ([]common.Hash, error) { + br := bufio.NewReader(r) + hashes := make([]common.Hash, 0) + buf := make([]byte, 0, common.HashLength) + for totalRead := uint64(0); totalRead < numToRead; totalRead++ { + n, err := br.Read(buf[:cap(buf)]) + if err != nil { + // If we try to read but reach EOF, we break out of the loop. + if err == io.EOF { + break + } + return nil, err + } + buf = buf[:n] + if n != common.HashLength { + return nil, fmt.Errorf("expected to read %d bytes, got %d bytes", common.HashLength, n) + } + hashes = append(hashes, common.BytesToHash(buf)) + } + if numToRead > uint64(len(hashes)) { + return nil, fmt.Errorf( + "wanted to read %d hashes, but only read %d hashes", + numToRead, + len(hashes), + ) + } + return hashes, nil +} + +func writeHashes(w io.Writer, hashes []common.Hash) error { + bw := bufio.NewWriter(w) + for i, rt := range hashes { + n, err := bw.Write(rt[:]) + if err != nil { + return err + } + if n != len(rt) { + return fmt.Errorf( + "for hash %d, wrote %d bytes, expected to write %d bytes", + i, + n, + len(rt), + ) + } + } + return bw.Flush() +} + +/* +* +When provided with a cache lookup struct, this function determines the file path +for the data requested within the cache directory hierarchy. The folder structure +for a given filesystem challenge cache will look as follows: + + wavm-module-root-0xab/ + rollup-block-hash-0x12...-message-num-70/ + hashes.bin + subchallenge-level-1-big-step-100/ + hashes.bin +*/ +func determineFilePath(baseDir string, lookup *Key) (string, error) { + key := make([]string, 0) + key = append(key, fmt.Sprintf("%s-%s", wavmModuleRootPrefix, lookup.WavmModuleRoot.Hex())) + key = append(key, fmt.Sprintf("%s-%s-%s-%d", rollupBlockHashPrefix, lookup.RollupBlockHash.Hex(), messageNumberPrefix, lookup.MessageHeight)) + for challengeLevel, height := range lookup.StepHeights { + key = append(key, fmt.Sprintf( + "%s-%d-%s-%d", + challengeLevelPrefix, + challengeLevel+1, // subchallenges start at 1, as level 0 is the block challenge level. + bigStepPrefix, + height, + ), + ) + + } + key = append(key, hashesFileName) + return filepath.Join(baseDir, filepath.Join(key...)), nil +} diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go new file mode 100644 index 000000000..6b15d62af --- /dev/null +++ b/staker/challenge-cache/cache_test.go @@ -0,0 +1,323 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +package challengecache + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +var _ HistoryCommitmentCacher = (*Cache)(nil) + +func TestCache(t *testing.T) { + basePath := t.TempDir() + if err := os.MkdirAll(basePath, os.ModePerm); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.RemoveAll(basePath); err != nil { + t.Fatal(err) + } + }) + cache, err := New(basePath) + if err != nil { + t.Fatal(err) + } + key := &Key{ + WavmModuleRoot: common.BytesToHash([]byte("foo")), + MessageHeight: 0, + StepHeights: []uint64{0}, + } + t.Run("Not found", func(t *testing.T) { + _, err := cache.Get(key, 0) + if !errors.Is(err, ErrNotFoundInCache) { + t.Fatal(err) + } + }) + t.Run("Putting empty hash fails", func(t *testing.T) { + if err := cache.Put(key, []common.Hash{}); !errors.Is(err, ErrNoHashes) { + t.Fatalf("Unexpected error: %v", err) + } + }) + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + err = cache.Put(key, want) + if err != nil { + t.Fatal(err) + } + got, err := cache.Get(key, 3) + if err != nil { + t.Fatal(err) + } + if len(got) != len(want) { + t.Fatalf("Wrong number of hashes. Expected %d, got %d", len(want), len(got)) + } + for i, rt := range got { + if rt != want[i] { + t.Fatalf("Wrong root. Expected %#x, got %#x", want[i], rt) + } + } +} + +func TestReadWriteStatehashes(t *testing.T) { + t.Run("read up to, but had empty reader", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + _, err := readHashes(b, 100) + if err == nil { + t.Fatal("Wanted error") + } + if !strings.Contains(err.Error(), "only read 0 hashes") { + t.Fatal("Unexpected error") + } + }) + t.Run("read single root", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + want := common.BytesToHash([]byte("foo")) + b.Write(want.Bytes()) + hashes, err := readHashes(b, 1) + if err != nil { + t.Fatal(err) + } + if len(hashes) == 0 { + t.Fatal("Got no hashes") + } + if hashes[0] != want { + t.Fatalf("Wrong root. Expected %#x, got %#x", want, hashes[0]) + } + }) + t.Run("Three hashes exist, want to read only two", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + foo := common.BytesToHash([]byte("foo")) + bar := common.BytesToHash([]byte("bar")) + baz := common.BytesToHash([]byte("baz")) + b.Write(foo.Bytes()) + b.Write(bar.Bytes()) + b.Write(baz.Bytes()) + hashes, err := readHashes(b, 2) + if err != nil { + t.Fatal(err) + } + if len(hashes) != 2 { + t.Fatalf("Expected two hashes, got %d", len(hashes)) + } + if hashes[0] != foo { + t.Fatalf("Wrong root. Expected %#x, got %#x", foo, hashes[0]) + } + if hashes[1] != bar { + t.Fatalf("Wrong root. Expected %#x, got %#x", bar, hashes[1]) + } + }) + t.Run("Fails to write enough data to writer", func(t *testing.T) { + m := &mockWriter{wantErr: true} + err := writeHashes(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + if err == nil { + t.Fatal("Wanted error") + } + m = &mockWriter{wantErr: false, numWritten: 16} + err = writeHashes(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + if err == nil { + t.Fatal("Wanted error") + } + if !strings.Contains(err.Error(), "short write") { + t.Fatalf("Got wrong error kind: %v", err) + } + }) +} + +type mockWriter struct { + wantErr bool + numWritten int +} + +func (m *mockWriter) Write(_ []byte) (n int, err error) { + if m.wantErr { + return 0, errors.New("something went wrong") + } + return m.numWritten, nil +} + +type mockReader struct { + wantErr bool + err error + hashes []common.Hash + readIdx int + bytesRead int +} + +func (m *mockReader) Read(out []byte) (n int, err error) { + if m.wantErr { + return 0, m.err + } + if m.readIdx == len(m.hashes) { + return 0, io.EOF + } + copy(out, m.hashes[m.readIdx].Bytes()) + m.readIdx++ + return m.bytesRead, nil +} + +func Test_readHashes(t *testing.T) { + t.Run("Unexpected error", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: true, hashes: want, err: errors.New("foo")} + _, err := readHashes(m, 1) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "foo") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("EOF, but did not read as much as was expected", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: true, hashes: want, err: io.EOF} + _, err := readHashes(m, 100) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "wanted to read 100") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("Reads wrong number of bytes", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: false, hashes: want, bytesRead: 16} + _, err := readHashes(m, 2) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "expected to read 32 bytes, got 16") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("Reads all until EOF", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: false, hashes: want, bytesRead: 32} + got, err := readHashes(m, 3) + if err != nil { + t.Fatal(err) + } + if len(want) != len(got) { + t.Fatal("Wrong number of hashes") + } + for i, rt := range got { + if rt != want[i] { + t.Fatal("Wrong root") + } + } + }) +} + +func Test_determineFilePath(t *testing.T) { + type args struct { + baseDir string + key *Key + } + tests := []struct { + name string + args args + want string + wantErr bool + errContains string + }{ + { + name: "OK", + args: args{ + baseDir: "", + key: &Key{ + MessageHeight: 100, + StepHeights: []uint64{50}, + }, + }, + want: "wavm-module-root-0x0000000000000000000000000000000000000000000000000000000000000000/rollup-block-hash-0x0000000000000000000000000000000000000000000000000000000000000000-message-num-100/subchallenge-level-1-big-step-50/hashes.bin", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := determineFilePath(tt.args.baseDir, tt.args.key) + if (err != nil) != tt.wantErr { + t.Logf("got: %v, and key %+v, got %s", err, tt.args.key, got) + if !strings.Contains(err.Error(), tt.errContains) { + t.Fatalf("Expected %s, got %s", tt.errContains, err.Error()) + } + t.Errorf("determineFilePath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf( + "determineFilePath() = %v, want %v", + got, + tt.want, + ) + } + }) + } +} + +func BenchmarkCache_Read_32Mb(b *testing.B) { + b.StopTimer() + basePath := os.TempDir() + if err := os.MkdirAll(basePath, os.ModePerm); err != nil { + b.Fatal(err) + } + b.Cleanup(func() { + if err := os.RemoveAll(basePath); err != nil { + b.Fatal(err) + } + }) + cache, err := New(basePath) + if err != nil { + b.Fatal(err) + } + key := &Key{ + WavmModuleRoot: common.BytesToHash([]byte("foo")), + MessageHeight: 0, + StepHeights: []uint64{0}, + } + numHashes := 1 << 20 + hashes := make([]common.Hash, numHashes) + for i := range hashes { + hashes[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + } + if err := cache.Put(key, hashes); err != nil { + b.Fatal(err) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + readUpTo := uint64(1 << 20) + hashes, err := cache.Get(key, readUpTo) + if err != nil { + b.Fatal(err) + } + if len(hashes) != numHashes { + b.Fatalf("Wrong number of hashes. Expected %d, got %d", hashes, len(hashes)) + } + } +} diff --git a/staker/challenge_manager.go b/staker/challenge_manager.go index ac2ae8835..22897e3c1 100644 --- a/staker/challenge_manager.go +++ b/staker/challenge_manager.go @@ -478,9 +478,18 @@ func (m *ChallengeManager) createExecutionBackend(ctx context.Context, step uint } } input.BatchInfo = prunedBatches - execRun, err := m.validator.execSpawner.CreateExecutionRun(m.wasmModuleRoot, input).Await(ctx) - if err != nil { - return fmt.Errorf("error creating execution backend for msg %v: %w", initialCount, err) + var execRun validator.ExecutionRun + for _, spawner := range m.validator.execSpawners { + if validator.SpawnerSupportsModule(spawner, m.wasmModuleRoot) { + execRun, err = spawner.CreateExecutionRun(m.wasmModuleRoot, input).Await(ctx) + if err != nil { + return fmt.Errorf("error creating execution backend for msg %v: %w", initialCount, err) + } + break + } + } + if execRun == nil { + return fmt.Errorf("did not find valid execution backend") } backend, err := NewExecutionChallengeBackend(execRun) if err != nil { diff --git a/staker/challenge_test.go b/staker/challenge_test.go index c21ebcdec..4534b04a2 100644 --- a/staker/challenge_test.go +++ b/staker/challenge_test.go @@ -5,6 +5,7 @@ package staker import ( "context" + "io" "math/big" "os" "path" @@ -116,9 +117,10 @@ func runChallengeTest( testTimeout bool, maxInboxMessage uint64, ) { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.LvlDebug) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(log.LevelDebug) + log.SetDefault(log.NewLogger(glogger)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -191,6 +193,7 @@ func runChallengeTest( for i := 0; i < 100; i++ { if testTimeout { + backend.Commit() err = backend.AdjustTime(time.Second * 40) } Require(t, err) @@ -247,7 +250,7 @@ func createBaseMachine(t *testing.T, wasmname string, wasmModules []string) *ser modulePaths = append(modulePaths, path.Join(wasmDir, moduleName)) } - machine, err := server_arb.LoadSimpleMachine(wasmPath, modulePaths) + machine, err := server_arb.LoadSimpleMachine(wasmPath, modulePaths, true) Require(t, err) return machine diff --git a/staker/l1_validator.go b/staker/l1_validator.go index 87fd4a669..d68365ede 100644 --- a/staker/l1_validator.go +++ b/staker/l1_validator.go @@ -10,9 +10,9 @@ import ( "math/big" "time" - "github.com/offchainlabs/nitro/eigenda" "github.com/offchainlabs/nitro/staker/txbuilder" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/validator" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -50,7 +50,6 @@ type L1Validator struct { wallet ValidatorWalletInterface callOpts bind.CallOpts - das eigenda.EigenDAReader inboxTracker InboxTrackerInterface txStreamer TransactionStreamerInterface blockValidator *BlockValidator @@ -62,7 +61,6 @@ func NewL1Validator( wallet ValidatorWalletInterface, validatorUtilsAddress common.Address, callOpts bind.CallOpts, - das eigenda.EigenDAReader, inboxTracker InboxTrackerInterface, txStreamer TransactionStreamerInterface, blockValidator *BlockValidator, @@ -90,7 +88,6 @@ func NewL1Validator( builder: builder, wallet: wallet, callOpts: callOpts, - das: das, inboxTracker: inboxTracker, txStreamer: txStreamer, blockValidator: blockValidator, @@ -191,12 +188,16 @@ func (v *L1Validator) resolveNextNode(ctx context.Context, info *StakerInfo, lat func (v *L1Validator) isRequiredStakeElevated(ctx context.Context) (bool, error) { callOpts := v.getCallOpts(ctx) - requiredStake, err := v.rollup.CurrentRequiredStake(callOpts) + baseStake, err := v.rollup.BaseStake(callOpts) if err != nil { return false, err } - baseStake, err := v.rollup.BaseStake(callOpts) + requiredStake, err := v.rollup.CurrentRequiredStake(callOpts) if err != nil { + if headerreader.ExecutionRevertedRegexp.MatchString(err.Error()) { + log.Warn("execution reverted checking if required state is elevated; assuming elevated", "err", err) + return true, nil + } return false, err } return requiredStake.Cmp(baseStake) > 0, nil @@ -339,10 +340,14 @@ func (v *L1Validator) generateNodeAction( batchNum = localBatchCount - 1 validatedCount = messageCount } else { - batchNum, err = FindBatchContainingMessageIndex(v.inboxTracker, validatedCount-1, localBatchCount) + var found bool + batchNum, found, err = v.inboxTracker.FindInboxBatchContainingMessage(validatedCount - 1) if err != nil { return nil, false, err } + if !found { + return nil, false, errors.New("batch not found on L1") + } } execResult, err := v.txStreamer.ResultAtCount(validatedCount) if err != nil { diff --git a/staker/staker.go b/staker/staker.go index f57ba3779..da6413e12 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -291,7 +291,7 @@ func NewStaker( } client := l1Reader.Client() val, err := NewL1Validator(client, wallet, validatorUtilsAddress, callOpts, - statelessBlockValidator.eigenDAService, statelessBlockValidator.inboxTracker, statelessBlockValidator.streamer, blockValidator) + statelessBlockValidator.inboxTracker, statelessBlockValidator.streamer, blockValidator) if err != nil { return nil, err } diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index 470d2b070..9dbc4dd33 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -7,45 +7,39 @@ import ( "context" "errors" "fmt" - "regexp" - "sync" "testing" - "github.com/offchainlabs/nitro/eigenda" - "github.com/offchainlabs/nitro/execution" - "github.com/offchainlabs/nitro/util/rpcclient" - "github.com/offchainlabs/nitro/validator/server_api" - - "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/util/rpcclient" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/client/redis" + + validatorclient "github.com/offchainlabs/nitro/validator/client" ) type StatelessBlockValidator struct { config *BlockValidatorConfig - execSpawner validator.ExecutionSpawner - validationSpawners []validator.ValidationSpawner + execSpawners []validator.ExecutionSpawner + redisValidator *redis.ValidationClient recorder execution.ExecutionRecorder - inboxReader InboxReaderInterface - inboxTracker InboxTrackerInterface - streamer TransactionStreamerInterface - db ethdb.Database - daService arbstate.DataAvailabilityReader - blobReader arbstate.BlobReader - eigenDAService eigenda.EigenDAReader - - moduleMutex sync.Mutex - currentWasmModuleRoot common.Hash - pendingWasmModuleRoot common.Hash + inboxReader InboxReaderInterface + inboxTracker InboxTrackerInterface + streamer TransactionStreamerInterface + db ethdb.Database + dapReaders []daprovider.Reader } type BlockValidatorRegistrer interface { @@ -58,6 +52,7 @@ type InboxTrackerInterface interface { GetBatchMessageCount(seqNum uint64) (arbutil.MessageIndex, error) GetBatchAcc(seqNum uint64) (common.Hash, error) GetBatchCount() (uint64, error) + FindInboxBatchContainingMessage(pos arbutil.MessageIndex) (uint64, bool, error) } type TransactionStreamerInterface interface { @@ -67,6 +62,7 @@ type TransactionStreamerInterface interface { ResultAtCount(count arbutil.MessageIndex) (*execution.MessageResult, error) PauseReorgs() ResumeReorgs() + ChainConfig() *params.ChainConfig } type InboxReaderInterface interface { @@ -110,39 +106,6 @@ func GlobalStatePositionsAtCount( return startPos, GlobalStatePosition{batch, posInBatch + 1}, nil } -func FindBatchContainingMessageIndex( - tracker InboxTrackerInterface, pos arbutil.MessageIndex, high uint64, -) (uint64, error) { - var low uint64 - // Iteration preconditions: - // - high >= low - // - msgCount(low - 1) <= pos implies low <= target - // - msgCount(high) > pos implies high >= target - // Therefore, if low == high, then low == high == target - for high > low { - // Due to integer rounding, mid >= low && mid < high - mid := (low + high) / 2 - count, err := tracker.GetBatchMessageCount(mid) - if err != nil { - return 0, err - } - if count < pos { - // Must narrow as mid >= low, therefore mid + 1 > low, therefore newLow > oldLow - // Keeps low precondition as msgCount(mid) < pos - low = mid + 1 - } else if count == pos { - return mid + 1, nil - } else if count == pos+1 || mid == low { // implied: count > pos - return mid, nil - } else { // implied: count > pos + 1 - // Must narrow as mid < high, therefore newHigh < lowHigh - // Keeps high precondition as msgCount(mid) > pos - high = mid - } - } - return low, nil -} - type ValidationEntryStage uint32 const ( @@ -159,12 +122,14 @@ type validationEntry struct { End validator.GoGlobalState HasDelayedMsg bool DelayedMsgNr uint64 + ChainConfig *params.ChainConfig // valid when created, removed after recording msg *arbostypes.MessageWithMetadata // Has batch when created - others could be added on record BatchInfo []validator.BatchInfo // Valid since Ready Preimages map[arbutil.PreimageType]map[common.Hash][]byte + UserWasms state.UserWasms DelayedMsg []byte } @@ -177,9 +142,11 @@ func (e *validationEntry) ToInput() (*validator.ValidationInput, error) { HasDelayedMsg: e.HasDelayedMsg, DelayedMsgNr: e.DelayedMsgNr, Preimages: e.Preimages, + UserWasms: e.UserWasms, BatchInfo: e.BatchInfo, DelayedMsg: e.DelayedMsg, StartState: e.Start, + DebugChain: e.ChainConfig.DebugMode(), }, nil } @@ -191,6 +158,7 @@ func newValidationEntry( batch []byte, batchBlockHash common.Hash, prevDelayed uint64, + chainConfig *params.ChainConfig, ) (*validationEntry, error) { batchInfo := validator.BatchInfo{ Number: start.Batch, @@ -214,6 +182,7 @@ func newValidationEntry( DelayedMsgNr: delayedNum, msg: msg, BatchInfo: []validator.BatchInfo{batchInfo}, + ChainConfig: chainConfig, }, nil } @@ -223,44 +192,42 @@ func NewStatelessBlockValidator( streamer TransactionStreamerInterface, recorder execution.ExecutionRecorder, arbdb ethdb.Database, - das arbstate.DataAvailabilityReader, - blobReader arbstate.BlobReader, - eigenDAService eigenda.EigenDAReader, + dapReaders []daprovider.Reader, config func() *BlockValidatorConfig, stack *node.Node, ) (*StatelessBlockValidator, error) { - validationSpawners := make([]validator.ValidationSpawner, len(config().ValidationServerConfigs)) - for i, serverConfig := range config().ValidationServerConfigs { - valConfFetcher := func() *rpcclient.ClientConfig { return &serverConfig } - validationSpawners[i] = server_api.NewValidationClient(valConfFetcher, stack) - } - valConfFetcher := func() *rpcclient.ClientConfig { return &config().ValidationServerConfigs[0] } - execClient := server_api.NewExecutionClient(valConfFetcher, stack) - validator := &StatelessBlockValidator{ - config: config(), - execSpawner: execClient, - recorder: recorder, - validationSpawners: validationSpawners, - inboxReader: inboxReader, - inboxTracker: inbox, - streamer: streamer, - db: arbdb, - eigenDAService: eigenDAService, - daService: das, - blobReader: blobReader, - } - return validator, nil -} + var executionSpawners []validator.ExecutionSpawner + var redisValClient *redis.ValidationClient -func (v *StatelessBlockValidator) GetModuleRootsToValidate() []common.Hash { - v.moduleMutex.Lock() - defer v.moduleMutex.Unlock() + if config().RedisValidationClientConfig.Enabled() { + var err error + redisValClient, err = redis.NewValidationClient(&config().RedisValidationClientConfig) + if err != nil { + return nil, fmt.Errorf("creating new redis validation client: %w", err) + } + } + configs := config().ValidationServerConfigs + for i := range configs { + i := i + confFetcher := func() *rpcclient.ClientConfig { return &config().ValidationServerConfigs[i] } + executionSpawners = append(executionSpawners, validatorclient.NewExecutionClient(confFetcher, stack)) + } - validatingModuleRoots := []common.Hash{v.currentWasmModuleRoot} - if (v.currentWasmModuleRoot != v.pendingWasmModuleRoot && v.pendingWasmModuleRoot != common.Hash{}) { - validatingModuleRoots = append(validatingModuleRoots, v.pendingWasmModuleRoot) + if len(executionSpawners) == 0 { + return nil, errors.New("no enabled execution servers") } - return validatingModuleRoots + + return &StatelessBlockValidator{ + config: config(), + recorder: recorder, + redisValidator: redisValClient, + inboxReader: inboxReader, + inboxTracker: inbox, + streamer: streamer, + db: arbdb, + dapReaders: dapReaders, + execSpawners: executionSpawners, + }, nil } func (v *StatelessBlockValidator) ValidationEntryRecord(ctx context.Context, e *validationEntry) error { @@ -281,6 +248,7 @@ func (v *StatelessBlockValidator) ValidationEntryRecord(ctx context.Context, e * if recording.Preimages != nil { e.Preimages[arbutil.Keccak256PreimageType] = recording.Preimages } + e.UserWasms = recording.UserWasms } if e.HasDelayedMsg { delayedMsg, err := v.inboxTracker.GetDelayedMessageBytes(e.DelayedMsgNr) @@ -297,45 +265,27 @@ func (v *StatelessBlockValidator) ValidationEntryRecord(ctx context.Context, e * if len(batch.Data) <= 40 { continue } - - if !arbstate.IsDASMessageHeaderByte(batch.Data[40]) && !arbstate.IsBlobHashesHeaderByte(batch.Data[40]) && eigenda.IsEigenDAMessageHeaderByte(batch.Data[40]) { - continue - } - - if arbstate.IsBlobHashesHeaderByte(batch.Data[40]) { - payload := batch.Data[41:] - if len(payload)%len(common.Hash{}) != 0 { - return fmt.Errorf("blob batch data is not a list of hashes as expected") - } - versionedHashes := make([]common.Hash, len(payload)/len(common.Hash{})) - for i := 0; i*32 < len(payload); i += 1 { - copy(versionedHashes[i][:], payload[i*32:(i+1)*32]) - } - blobs, err := v.blobReader.GetBlobs(ctx, batch.BlockHash, versionedHashes) - if err != nil { - return fmt.Errorf("failed to get blobs: %w", err) - } - if e.Preimages[arbutil.EthVersionedHashPreimageType] == nil { - e.Preimages[arbutil.EthVersionedHashPreimageType] = make(map[common.Hash][]byte) - } - for i, blob := range blobs { - // Prevent aliasing `blob` when slicing it, as for range loops overwrite the same variable - // Won't be necessary after Go 1.22 with https://go.dev/blog/loopvar-preview - b := blob - e.Preimages[arbutil.EthVersionedHashPreimageType][versionedHashes[i]] = b[:] - } - } - - if arbstate.IsDASMessageHeaderByte(batch.Data[40]) { - if v.daService == nil { - log.Warn("No DAS configured, but sequencer message found with DAS header") - } else { - _, err := arbstate.RecoverPayloadFromDasBatch( - ctx, batch.Number, batch.Data, v.daService, e.Preimages, arbstate.KeysetValidate, - ) + foundDA := false + for _, dapReader := range v.dapReaders { + if dapReader != nil && dapReader.IsValidHeaderByte(batch.Data[40]) { + preimageRecorder := daprovider.RecordPreimagesTo(e.Preimages) + _, err := dapReader.RecoverPayloadFromBatch(ctx, batch.Number, batch.BlockHash, batch.Data, preimageRecorder, true) if err != nil { - return err + // Matches the way keyset validation was done inside DAS readers i.e logging the error + // But other daproviders might just want to return the error + if errors.Is(err, daprovider.ErrSeqMsgValidation) && daprovider.IsDASMessageHeaderByte(batch.Data[40]) { + log.Error(err.Error()) + } else { + return err + } } + foundDA = true + break + } + } + if !foundDA { + if daprovider.IsDASMessageHeaderByte(batch.Data[40]) { + log.Error("No DAS Reader configured, but sequencer message found with DAS header") } } @@ -377,13 +327,12 @@ func (v *StatelessBlockValidator) GlobalStatePositionsAtCount(count arbutil.Mess if count == 1 { return GlobalStatePosition{}, GlobalStatePosition{1, 0}, nil } - batchCount, err := v.inboxTracker.GetBatchCount() + batch, found, err := v.inboxTracker.FindInboxBatchContainingMessage(count - 1) if err != nil { return GlobalStatePosition{}, GlobalStatePosition{}, err } - batch, err := FindBatchContainingMessageIndex(v.inboxTracker, count-1, batchCount) - if err != nil { - return GlobalStatePosition{}, GlobalStatePosition{}, err + if !found { + return GlobalStatePosition{}, GlobalStatePosition{}, errors.New("batch not found on L1 yet") } return GlobalStatePositionsAtCount(v.inboxTracker, count, batch) } @@ -419,7 +368,7 @@ func (v *StatelessBlockValidator) CreateReadyValidationEntry(ctx context.Context if err != nil { return nil, err } - entry, err := newValidationEntry(pos, start, end, msg, seqMsg, batchBlockHash, prevDelayed) + entry, err := newValidationEntry(pos, start, end, msg, seqMsg, batchBlockHash, prevDelayed, v.streamer.ChainConfig()) if err != nil { return nil, err } @@ -442,30 +391,29 @@ func (v *StatelessBlockValidator) ValidateResult( if err != nil { return false, nil, err } - var spawners []validator.ValidationSpawner - if useExec { - spawners = append(spawners, v.execSpawner) - } else { - spawners = v.validationSpawners + var run validator.ValidationRun + if !useExec { + if v.redisValidator != nil { + if validator.SpawnerSupportsModule(v.redisValidator, moduleRoot) { + run = v.redisValidator.Launch(input, moduleRoot) + } + } } - if len(spawners) == 0 { - return false, &entry.End, errors.New("no validation defined") + if run == nil { + for _, spawner := range v.execSpawners { + if validator.SpawnerSupportsModule(spawner, moduleRoot) { + run = spawner.Launch(input, moduleRoot) + break + } + } } - var runs []validator.ValidationRun - for _, spawner := range spawners { - run := spawner.Launch(input, moduleRoot) - runs = append(runs, run) + if run == nil { + return false, nil, fmt.Errorf("validation with WasmModuleRoot %v not supported by node", moduleRoot) } - defer func() { - for _, run := range runs { - run.Cancel() - } - }() - for _, run := range runs { - gsEnd, err := run.Await(ctx) - if err != nil || gsEnd != entry.End { - return false, &gsEnd, err - } + defer run.Cancel() + gsEnd, err := run.Await(ctx) + if err != nil || gsEnd != entry.End { + return false, &gsEnd, err } return true, &entry.End, nil } @@ -474,37 +422,40 @@ func (v *StatelessBlockValidator) OverrideRecorder(t *testing.T, recorder execut v.recorder = recorder } +func (v *StatelessBlockValidator) GetLatestWasmModuleRoot(ctx context.Context) (common.Hash, error) { + var lastErr error + for _, spawner := range v.execSpawners { + var latest common.Hash + latest, lastErr = spawner.LatestWasmModuleRoot().Await(ctx) + if latest != (common.Hash{}) && lastErr == nil { + return latest, nil + } + if ctx.Err() != nil { + return common.Hash{}, ctx.Err() + } + } + return common.Hash{}, fmt.Errorf("couldn't detect latest WasmModuleRoot: %w", lastErr) +} + func (v *StatelessBlockValidator) Start(ctx_in context.Context) error { - err := v.execSpawner.Start(ctx_in) - if err != nil { - return err + if v.redisValidator != nil { + if err := v.redisValidator.Start(ctx_in); err != nil { + return fmt.Errorf("starting execution spawner: %w", err) + } } - for _, spawner := range v.validationSpawners { + for _, spawner := range v.execSpawners { if err := spawner.Start(ctx_in); err != nil { return err } } - if v.config.PendingUpgradeModuleRoot != "" { - if v.config.PendingUpgradeModuleRoot == "latest" { - latest, err := v.execSpawner.LatestWasmModuleRoot().Await(ctx_in) - if err != nil { - return err - } - v.pendingWasmModuleRoot = latest - } else { - valid, _ := regexp.MatchString("(0x)?[0-9a-fA-F]{64}", v.config.PendingUpgradeModuleRoot) - v.pendingWasmModuleRoot = common.HexToHash(v.config.PendingUpgradeModuleRoot) - if (!valid || v.pendingWasmModuleRoot == common.Hash{}) { - return errors.New("pending-upgrade-module-root config value illegal") - } - } - } return nil } func (v *StatelessBlockValidator) Stop() { - v.execSpawner.Stop() - for _, spawner := range v.validationSpawners { + for _, spawner := range v.execSpawners { spawner.Stop() } + if v.redisValidator != nil { + v.redisValidator.Stop() + } } diff --git a/staker/validatorwallet/contract.go b/staker/validatorwallet/contract.go index 0ef190e70..77b403b66 100644 --- a/staker/validatorwallet/contract.go +++ b/staker/validatorwallet/contract.go @@ -307,6 +307,7 @@ func (v *Contract) estimateGas(ctx context.Context, value *big.Int, data []byte) if err != nil { return 0, fmt.Errorf("getting suggested gas tip cap: %w", err) } + gasFeeCap.Add(gasFeeCap, gasTipCap) g, err := v.l1Reader.Client().EstimateGas( ctx, ethereum.CallMsg{ diff --git a/system_tests/batch_poster_test.go b/system_tests/batch_poster_test.go index 0fc127d0e..0ec03e84c 100644 --- a/system_tests/batch_poster_test.go +++ b/system_tests/batch_poster_test.go @@ -8,7 +8,6 @@ import ( "crypto/rand" "fmt" "math/big" - "net/http" "strings" "testing" "time" @@ -62,14 +61,14 @@ func addNewBatchPoster(ctx context.Context, t *testing.T, builder *NodeBuilder, } } -func externalSignerTestCfg(addr common.Address) (*dataposter.ExternalSignerCfg, error) { +func externalSignerTestCfg(addr common.Address, url string) (*dataposter.ExternalSignerCfg, error) { cp, err := externalsignertest.CertPaths() if err != nil { return nil, fmt.Errorf("getting certificates path: %w", err) } return &dataposter.ExternalSignerCfg{ Address: common.Bytes2Hex(addr.Bytes()), - URL: externalsignertest.SignerURL, + URL: url, Method: externalsignertest.SignerMethod, RootCA: cp.ServerCert, ClientCert: cp.ClientCert, @@ -80,24 +79,13 @@ func externalSignerTestCfg(addr common.Address) (*dataposter.ExternalSignerCfg, func testBatchPosterParallel(t *testing.T, useRedis bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - httpSrv, srv := externalsignertest.NewServer(t) - cp, err := externalsignertest.CertPaths() - if err != nil { - t.Fatalf("Error getting cert paths: %v", err) - } - t.Cleanup(func() { - if err := httpSrv.Shutdown(ctx); err != nil { - t.Fatalf("Error shutting down http server: %v", err) - } - }) + srv := externalsignertest.NewServer(t) go func() { - log.Debug("Server is listening on port 1234...") - if err := httpSrv.ListenAndServeTLS(cp.ServerCert, cp.ServerKey); err != nil && err != http.ErrServerClosed { - log.Debug("ListenAndServeTLS() failed", "error", err) + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) return } }() - var redisUrl string if useRedis { redisUrl = redisutil.CreateTestRedis(ctx, t) @@ -114,7 +102,7 @@ func testBatchPosterParallel(t *testing.T, useRedis bool) { builder := NewNodeBuilder(ctx).DefaultConfig(t, true) builder.nodeConfig.BatchPoster.Enable = false builder.nodeConfig.BatchPoster.RedisUrl = redisUrl - signerCfg, err := externalSignerTestCfg(srv.Address) + signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) if err != nil { t.Fatalf("Error getting external signer config: %v", err) } @@ -171,7 +159,7 @@ func testBatchPosterParallel(t *testing.T, useRedis bool) { Config: func() *arbnode.BatchPosterConfig { return &batchPosterConfig }, DeployInfo: builder.L2.ConsensusNode.DeployInfo, TransactOpts: &seqTxOpts, - DAWriter: nil, + DAPWriter: nil, ParentChainID: parentChainID, }, ) @@ -303,3 +291,75 @@ func TestBatchPosterKeepsUp(t *testing.T) { fmt.Printf("backlog: %v message\n", haveMessages-postedMessages) } } + +func testAllowPostingFirstBatchWhenSequencerMessageCountMismatch(t *testing.T, enabled bool) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // creates first node with batch poster disabled + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.nodeConfig.BatchPoster.Enable = false + cleanup := builder.Build(t) + defer cleanup() + testClientNonBatchPoster := builder.L2 + + // adds a batch to the sequencer inbox with a wrong next message count, + // should be 2 but it is set to 10 + seqInbox, err := bridgegen.NewSequencerInbox(builder.L1Info.GetAddress("SequencerInbox"), builder.L1.Client) + Require(t, err) + seqOpts := builder.L1Info.GetDefaultTransactOpts("Sequencer", ctx) + tx, err := seqInbox.AddSequencerL2Batch(&seqOpts, big.NewInt(1), nil, big.NewInt(1), common.Address{}, big.NewInt(1), big.NewInt(10)) + Require(t, err) + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + // creates a batch poster + nodeConfigBatchPoster := arbnode.ConfigDefaultL1Test() + nodeConfigBatchPoster.BatchPoster.Dangerous.AllowPostingFirstBatchWhenSequencerMessageCountMismatch = enabled + testClientBatchPoster, cleanupBatchPoster := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: nodeConfigBatchPoster}) + defer cleanupBatchPoster() + + // sends a transaction through the batch poster + accountName := "User2" + builder.L2Info.GenerateAccount(accountName) + tx = builder.L2Info.PrepareTx("Owner", accountName, builder.L2Info.TransferGas, big.NewInt(1e12), nil) + err = testClientBatchPoster.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = testClientBatchPoster.EnsureTxSucceeded(tx) + Require(t, err) + + if enabled { + // if AllowPostingFirstBatchWhenSequencerMessageCountMismatch is enabled + // then the L2 transaction should be posted to L1, and the non batch + // poster node should be able to see it + _, err = WaitForTx(ctx, testClientNonBatchPoster.Client, tx.Hash(), time.Second*3) + Require(t, err) + l2balance, err := testClientNonBatchPoster.Client.BalanceAt(ctx, builder.L2Info.GetAddress(accountName), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(1e12)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } + } else { + // if AllowPostingFirstBatchWhenSequencerMessageCountMismatch is disabled + // then the L2 transaction should not be posted to L1, so the non + // batch poster will not be able to see it + _, err = WaitForTx(ctx, testClientNonBatchPoster.Client, tx.Hash(), time.Second*3) + if err == nil { + Fatal(t, "tx received by non batch poster node with AllowPostingFirstBatchWhenSequencerMessageCountMismatch disabled") + } + l2balance, err := testClientNonBatchPoster.Client.BalanceAt(ctx, builder.L2Info.GetAddress(accountName), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(0)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } + } +} + +func TestAllowPostingFirstBatchWhenSequencerMessageCountMismatchEnabled(t *testing.T) { + testAllowPostingFirstBatchWhenSequencerMessageCountMismatch(t, true) +} + +func TestAllowPostingFirstBatchWhenSequencerMessageCountMismatchDisabled(t *testing.T) { + testAllowPostingFirstBatchWhenSequencerMessageCountMismatch(t, false) +} diff --git a/system_tests/benchmarks_test.go b/system_tests/benchmarks_test.go new file mode 100644 index 000000000..64ce1fe2f --- /dev/null +++ b/system_tests/benchmarks_test.go @@ -0,0 +1,64 @@ +// Copyright 2022-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build benchmarks +// +build benchmarks + +package arbtest + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" +) + +func TestBenchmarkGas(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l2info, l2node, l2client, _, _, _, l1stack := createTestNodeOnL1(t, ctx, true) + defer requireClose(t, l1stack) + defer l2node.StopAndWait() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + return EnsureTxFailed(t, ctx, l2client, tx) + } + + auth := l2info.GetDefaultTransactOpts("Faucet", ctx) + auth.GasLimit = 32000000 + + var programTest *mocksgen.Benchmarks + timed(t, "deploy", func() { + _, _, contract, err := mocksgen.DeployBenchmarks(&auth, l2client) + Require(t, err) + programTest = contract + }) + bench := func(name string, lambda func() *types.Receipt) { + now := time.Now() + receipt := lambda() + passed := time.Since(now) + ratio := float64(passed.Nanoseconds()) / float64(receipt.GasUsedForL2()) + fmt.Printf("Bench %-10v %v %.2f ns/gas\n", name, formatTime(passed), ratio) + } + bench("ecrecover", func() *types.Receipt { + return ensure(programTest.FillBlockRecover(&auth)) + }) + bench("mulmod", func() *types.Receipt { + return ensure(programTest.FillBlockMulMod(&auth)) + }) + bench("keccak", func() *types.Receipt { + return ensure(programTest.FillBlockHash(&auth)) + }) + bench("add", func() *types.Receipt { + return ensure(programTest.FillBlockAdd(&auth)) + }) + bench("quick step", func() *types.Receipt { + return ensure(programTest.FillBlockQuickStep(&auth)) + }) +} diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index 1fcf2bab3..54046edf1 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -26,6 +26,8 @@ import ( "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/validator/client/redis" ) type workloadType uint @@ -37,7 +39,7 @@ const ( upgradeArbOs ) -func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops int, workload workloadType, arbitrator bool) { +func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops int, workload workloadType, arbitrator bool, useRedisStreams bool) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -67,7 +69,17 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops validatorConfig.BlockValidator.Enable = true validatorConfig.DataAvailability = l1NodeConfigA.DataAvailability validatorConfig.DataAvailability.RPCAggregator.Enable = false - AddDefaultValNode(t, ctx, validatorConfig, !arbitrator) + redisURL := "" + if useRedisStreams { + redisURL = redisutil.CreateTestRedis(ctx, t) + validatorConfig.BlockValidator.RedisValidationClientConfig = redis.TestValidationClientConfig + validatorConfig.BlockValidator.RedisValidationClientConfig.RedisURL = redisURL + } else { + validatorConfig.BlockValidator.RedisValidationClientConfig = redis.ValidationClientConfig{} + } + + AddDefaultValNode(t, ctx, validatorConfig, !arbitrator, redisURL) + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) defer cleanupB() builder.L2Info.GenerateAccount("User2") @@ -239,17 +251,21 @@ func testBlockValidatorSimple(t *testing.T, dasModeString string, workloadLoops } func TestBlockValidatorSimpleOnchainUpgradeArbOs(t *testing.T) { - testBlockValidatorSimple(t, "onchain", 1, upgradeArbOs, true) + testBlockValidatorSimple(t, "onchain", 1, upgradeArbOs, true, false) } func TestBlockValidatorSimpleOnchain(t *testing.T) { - testBlockValidatorSimple(t, "onchain", 1, ethSend, true) + testBlockValidatorSimple(t, "onchain", 1, ethSend, true, false) +} + +func TestBlockValidatorSimpleOnchainWithRedisStreams(t *testing.T) { + testBlockValidatorSimple(t, "onchain", 1, ethSend, true, true) } func TestBlockValidatorSimpleLocalDAS(t *testing.T) { - testBlockValidatorSimple(t, "files", 1, ethSend, true) + testBlockValidatorSimple(t, "files", 1, ethSend, true, false) } func TestBlockValidatorSimpleJITOnchain(t *testing.T) { - testBlockValidatorSimple(t, "files", 8, smallContract, false) + testBlockValidatorSimple(t, "files", 8, smallContract, false, false) } diff --git a/system_tests/blocks_reexecutor_test.go b/system_tests/blocks_reexecutor_test.go index c2941ddcc..66690d142 100644 --- a/system_tests/blocks_reexecutor_test.go +++ b/system_tests/blocks_reexecutor_test.go @@ -45,16 +45,11 @@ func TestBlocksReExecutorModes(t *testing.T) { } } + // Reexecute blocks at mode full success := make(chan struct{}) + executorFull := blocksreexecutor.New(&blocksreexecutor.TestConfig, blockchain, feedErrChan) + executorFull.Start(ctx, success) - // Reexecute blocks at mode full - go func() { - executorFull := blocksreexecutor.New(&blocksreexecutor.TestConfig, blockchain, feedErrChan) - executorFull.StopWaiter.Start(ctx, executorFull) - executorFull.Impl(ctx) - executorFull.StopAndWait() - success <- struct{}{} - }() select { case err := <-feedErrChan: t.Errorf("error occurred: %v", err) @@ -66,15 +61,12 @@ func TestBlocksReExecutorModes(t *testing.T) { } // Reexecute blocks at mode random - go func() { - c := &blocksreexecutor.TestConfig - c.Mode = "random" - executorRandom := blocksreexecutor.New(c, blockchain, feedErrChan) - executorRandom.StopWaiter.Start(ctx, executorRandom) - executorRandom.Impl(ctx) - executorRandom.StopAndWait() - success <- struct{}{} - }() + success = make(chan struct{}) + c := &blocksreexecutor.TestConfig + c.Mode = "random" + executorRandom := blocksreexecutor.New(c, blockchain, feedErrChan) + executorRandom.Start(ctx, success) + select { case err := <-feedErrChan: t.Errorf("error occurred: %v", err) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index fb1a7f701..d7e61c0c7 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbtest @@ -8,6 +8,7 @@ import ( "context" "encoding/hex" "encoding/json" + "io" "math/big" "net" "os" @@ -16,29 +17,38 @@ import ( "testing" "time" + "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/util" - "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/das" "github.com/offchainlabs/nitro/deploy" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" + rediscons "github.com/offchainlabs/nitro/validator/valnode/redis" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/arbitrum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/downloader" @@ -57,13 +67,14 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbutil" - _ "github.com/offchainlabs/nitro/nodeInterface" + _ "github.com/offchainlabs/nitro/execution/nodeInterface" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" "github.com/offchainlabs/nitro/statetransfer" "github.com/offchainlabs/nitro/util/testhelpers" + "golang.org/x/exp/slog" ) type info = *BlockchainTestInfo @@ -183,6 +194,13 @@ func (b *NodeBuilder) DefaultConfig(t *testing.T, withL1 bool) *NodeBuilder { return b } +func (b *NodeBuilder) WithArbOSVersion(arbosVersion uint64) *NodeBuilder { + newChainConfig := *b.chainConfig + newChainConfig.ArbitrumChainParams.InitialArbOSVersion = arbosVersion + b.chainConfig = &newChainConfig + return b +} + func (b *NodeBuilder) Build(t *testing.T) func() { if b.execConfig.RPC.MaxRecreateStateDepth == arbitrum.UninitializedMaxRecreateStateDepth { if b.execConfig.Caching.Archive { @@ -504,6 +522,24 @@ func createStackConfigForTest(dataDir string) *node.Config { return &stackConf } +func createRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { + t.Helper() + // Stream name and group name are the same. + if _, err := client.XGroupCreateMkStream(ctx, streamName, streamName, "$").Result(); err != nil { + log.Debug("Error creating stream group: %v", err) + } +} + +func destroyRedisGroup(ctx context.Context, t *testing.T, streamName string, client redis.UniversalClient) { + t.Helper() + if client == nil { + return + } + if _, err := client.XGroupDestroy(ctx, streamName, streamName).Result(); err != nil { + log.Debug("Error destroying a stream group", "error", err) + } +} + func createTestValidationNode(t *testing.T, ctx context.Context, config *valnode.Config) (*valnode.ValidationNode, *node.Node) { stackConf := node.DefaultConfig stackConf.HTTPPort = 0 @@ -555,22 +591,41 @@ func StaticFetcherFrom[T any](t *testing.T, config *T) func() *T { return func() *T { return &tCopy } } -func configByValidationNode(t *testing.T, clientConfig *arbnode.Config, valStack *node.Node) { +func configByValidationNode(clientConfig *arbnode.Config, valStack *node.Node) { clientConfig.BlockValidator.ValidationServerConfigs[0].URL = valStack.WSEndpoint() clientConfig.BlockValidator.ValidationServerConfigs[0].JWTSecret = "" } -func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Config, useJit bool) { - if !nodeConfig.ValidatorRequired() { - return +func currentRootModule(t *testing.T) common.Hash { + t.Helper() + locator, err := server_common.NewMachineLocator("") + if err != nil { + t.Fatalf("Error creating machine locator: %v", err) } - if nodeConfig.BlockValidator.ValidationServerConfigs[0].URL != "" { + return locator.LatestWasmModuleRoot() +} + +func AddDefaultValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Config, useJit bool, redisURL string) { + if !nodeConfig.ValidatorRequired() { return } conf := valnode.TestValidationConfig conf.UseJit = useJit + // Enable redis streams when URL is specified + if redisURL != "" { + conf.Arbitrator.RedisValidationServerConfig = rediscons.DefaultValidationServerConfig + redisClient, err := redisutil.RedisClientFromURL(redisURL) + if err != nil { + t.Fatalf("Error creating redis coordinator: %v", err) + } + redisStream := server_api.RedisStreamForRoot(currentRootModule(t)) + createRedisGroup(ctx, t, redisStream, redisClient) + conf.Arbitrator.RedisValidationServerConfig.RedisURL = redisURL + t.Cleanup(func() { destroyRedisGroup(ctx, t, redisStream, redisClient) }) + conf.Arbitrator.RedisValidationServerConfig.ModuleRoots = []string{currentRootModule(t).Hex()} + } _, valStack := createTestValidationNode(t, ctx, &conf) - configByValidationNode(t, nodeConfig, valStack) + configByValidationNode(nodeConfig, valStack) } func createTestL1BlockChainWithConfig(t *testing.T, l1info info, stackConfig *node.Config) (info, *ethclient.Client, *eth.Ethereum, *node.Node) { @@ -590,7 +645,8 @@ func createTestL1BlockChainWithConfig(t *testing.T, l1info info, stackConfig *no nodeConf := ethconfig.Defaults nodeConf.NetworkId = chainConfig.ChainID.Uint64() - l1Genesis := core.DeveloperGenesisBlock(15_000_000, l1info.GetAddress("Faucet")) + faucetAddr := l1info.GetAddress("Faucet") + l1Genesis := core.DeveloperGenesisBlock(15_000_000, &faucetAddr) infoGenesis := l1info.GetGenesisAlloc() for acct, info := range infoGenesis { l1Genesis.Alloc[acct] = info @@ -656,11 +712,13 @@ func DeployOnTestL1( ) (*chaininfo.RollupAddresses, *arbostypes.ParsedInitMessage) { l1info.GenerateAccount("RollupOwner") l1info.GenerateAccount("Sequencer") + l1info.GenerateAccount("Validator") l1info.GenerateAccount("User") SendWaitTestTransactions(t, ctx, l1client, []*types.Transaction{ l1info.PrepareTx("Faucet", "RollupOwner", 30000, big.NewInt(9223372036854775807), nil), l1info.PrepareTx("Faucet", "Sequencer", 30000, big.NewInt(9223372036854775807), nil), + l1info.PrepareTx("Faucet", "Validator", 30000, big.NewInt(9223372036854775807), nil), l1info.PrepareTx("Faucet", "User", 30000, big.NewInt(9223372036854775807), nil)}) l1TransactionOpts := l1info.GetDefaultTransactOpts("RollupOwner", ctx) @@ -720,9 +778,12 @@ func createL2BlockChainWithStackConfig( stack, err = node.New(stackConfig) Require(t, err) - chainDb, err := stack.OpenDatabase("chaindb", 0, 0, "", false) + chainData, err := stack.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) Require(t, err) - arbDb, err := stack.OpenDatabase("arbitrumdata", 0, 0, "", false) + wasmData, err := stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) + Require(t, err) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 0) + arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) initReader := statetransfer.NewMemoryInitDataReader(&l2info.ArbInitData) @@ -800,7 +861,13 @@ func createTestNodeWithL1( execConfig.Sequencer.Enable = false } - AddDefaultValNode(t, ctx, nodeConfig, true) + var validatorTxOptsPtr *bind.TransactOpts + if nodeConfig.Staker.Enable { + validatorTxOpts := l1info.GetDefaultTransactOpts("Validator", ctx) + validatorTxOptsPtr = &validatorTxOpts + } + + AddDefaultValNode(t, ctx, nodeConfig, true, "") Require(t, execConfig.Validate()) execConfigFetcher := func() *gethexec.Config { return execConfig } @@ -808,7 +875,7 @@ func createTestNodeWithL1( Require(t, err) currentNode, err = arbnode.CreateNode( ctx, l2stack, execNode, l2arbDb, NewFetcherFromConfig(nodeConfig), l2blockchain.Config(), l1client, - addresses, sequencerTxOptsPtr, sequencerTxOptsPtr, dataSigner, fatalErrChan, big.NewInt(1337), nil, + addresses, validatorTxOptsPtr, sequencerTxOptsPtr, dataSigner, fatalErrChan, big.NewInt(1337), nil, ) Require(t, err) @@ -835,7 +902,7 @@ func createTestNode( feedErrChan := make(chan error, 10) - AddDefaultValNode(t, ctx, nodeConfig, true) + AddDefaultValNode(t, ctx, nodeConfig, true, "") l2info, stack, chainDb, arbDb, blockchain := createL2BlockChain(t, l2Info, "", chainConfig, &execConfig.Caching) @@ -924,14 +991,19 @@ func Create2ndNodeWithConfig( l2stack, err := node.New(stackConfig) Require(t, err) - l2chainDb, err := l2stack.OpenDatabase("chaindb", 0, 0, "", false) + l2chainData, err := l2stack.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) Require(t, err) - l2arbDb, err := l2stack.OpenDatabase("arbitrumdata", 0, 0, "", false) + wasmData, err := l2stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) + Require(t, err) + l2chainDb := rawdb.WrapDatabaseWithWasm(l2chainData, wasmData, 0) + + l2arbDb, err := l2stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) dataSigner := signature.DataSignerFromPrivateKey(l1info.GetInfoWithPrivKey("Sequencer").PrivateKey) - txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) + sequencerTxOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) + validatorTxOpts := l1info.GetDefaultTransactOpts("Validator", ctx) firstExec := getExecNode(t, first) chainConfig := firstExec.ArbInterface.BlockChain().Config() @@ -941,7 +1013,7 @@ func Create2ndNodeWithConfig( l2blockchain, err := gethexec.WriteOrTestBlockChain(l2chainDb, coreCacheConfig, initReader, chainConfig, initMessage, gethexec.ConfigDefaultTest().TxLookupLimit, 0) Require(t, err) - AddDefaultValNode(t, ctx, nodeConfig, true) + AddDefaultValNode(t, ctx, nodeConfig, true, "") Require(t, execConfig.Validate()) Require(t, nodeConfig.Validate()) @@ -949,7 +1021,7 @@ func Create2ndNodeWithConfig( currentExec, err := gethexec.CreateExecutionNode(ctx, l2stack, l2chainDb, l2blockchain, l1client, configFetcher) Require(t, err) - currentNode, err := arbnode.CreateNode(ctx, l2stack, currentExec, l2arbDb, NewFetcherFromConfig(nodeConfig), l2blockchain.Config(), l1client, first.DeployInfo, &txOpts, &txOpts, dataSigner, feedErrChan, big.NewInt(1337), nil) + currentNode, err := arbnode.CreateNode(ctx, l2stack, currentExec, l2arbDb, NewFetcherFromConfig(nodeConfig), l2blockchain.Config(), l1client, first.DeployInfo, &validatorTxOpts, &sequencerTxOpts, dataSigner, feedErrChan, big.NewInt(1337), nil) Require(t, err) err = currentNode.Start(ctx) @@ -983,7 +1055,7 @@ func authorizeDASKeyset( if dasSignerKey == nil { return } - keyset := &arbstate.DataAvailabilityKeyset{ + keyset := &daprovider.DataAvailabilityKeyset{ AssumedHonest: 1, PubKeys: []blsSignatures.PublicKey{*dasSignerKey}, } @@ -1059,15 +1131,16 @@ func setupConfigWithDAS( var daReader das.DataAvailabilityServiceReader var daWriter das.DataAvailabilityServiceWriter var daHealthChecker das.DataAvailabilityServiceHealthChecker + var signatureVerifier *das.SignatureVerifier if dasModeString != "onchain" { - daReader, daWriter, daHealthChecker, lifecycleManager, err = das.CreateDAComponentsForDaserver(ctx, dasConfig, nil, nil) + daReader, daWriter, signatureVerifier, daHealthChecker, lifecycleManager, err = das.CreateDAComponentsForDaserver(ctx, dasConfig, nil, nil) Require(t, err) rpcLis, err := net.Listen("tcp", "localhost:0") Require(t, err) restLis, err := net.Listen("tcp", "localhost:0") Require(t, err) - _, err = das.StartDASRPCServerOnListener(ctx, rpcLis, genericconf.HTTPServerTimeoutConfigDefault, daReader, daWriter, daHealthChecker) + _, err = das.StartDASRPCServerOnListener(ctx, rpcLis, genericconf.HTTPServerTimeoutConfigDefault, genericconf.HTTPServerBodyLimitDefault, daReader, daWriter, daHealthChecker, signatureVerifier) Require(t, err) _, err = das.NewRestfulDasServerOnListener(restLis, genericconf.HTTPServerTimeoutConfigDefault, daReader, daHealthChecker) Require(t, err) @@ -1113,16 +1186,87 @@ func deploySimple( return addr, simple } +func deployContractInitCode(code []byte, revert bool) []byte { + // a small prelude to return the given contract code + last_opcode := vm.RETURN + if revert { + last_opcode = vm.REVERT + } + deploy := []byte{byte(vm.PUSH32)} + deploy = append(deploy, math.U256Bytes(big.NewInt(int64(len(code))))...) + deploy = append(deploy, byte(vm.DUP1)) + deploy = append(deploy, byte(vm.PUSH1)) + deploy = append(deploy, 42) // the prelude length + deploy = append(deploy, byte(vm.PUSH1)) + deploy = append(deploy, 0) + deploy = append(deploy, byte(vm.CODECOPY)) + deploy = append(deploy, byte(vm.PUSH1)) + deploy = append(deploy, 0) + deploy = append(deploy, byte(last_opcode)) + deploy = append(deploy, code...) + return deploy +} + +func deployContract( + t *testing.T, ctx context.Context, auth bind.TransactOpts, client *ethclient.Client, code []byte, +) common.Address { + deploy := deployContractInitCode(code, false) + basefee := arbmath.BigMulByFrac(GetBaseFee(t, client, ctx), 6, 5) // current*1.2 + nonce, err := client.NonceAt(ctx, auth.From, nil) + Require(t, err) + gas, err := client.EstimateGas(ctx, ethereum.CallMsg{ + From: auth.From, + GasPrice: basefee, + GasTipCap: auth.GasTipCap, + Value: big.NewInt(0), + Data: deploy, + }) + Require(t, err) + tx := types.NewContractCreation(nonce, big.NewInt(0), gas, basefee, deploy) + tx, err = auth.Signer(auth.From, tx) + Require(t, err) + Require(t, client.SendTransaction(ctx, tx)) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + return crypto.CreateAddress(auth.From, nonce) +} + +func sendContractCall( + t *testing.T, ctx context.Context, to common.Address, client *ethclient.Client, data []byte, +) []byte { + t.Helper() + msg := ethereum.CallMsg{ + To: &to, + Value: big.NewInt(0), + Data: data, + } + res, err := client.CallContract(ctx, msg, nil) + Require(t, err) + return res +} + +func doUntil(t *testing.T, delay time.Duration, max int, lambda func() bool) { + t.Helper() + for i := 0; i < max; i++ { + if lambda() { + return + } + time.Sleep(delay) + } + Fatal(t, "failed to complete after ", delay*time.Duration(max)) +} + func TestMain(m *testing.M) { logLevelEnv := os.Getenv("TEST_LOGLEVEL") if logLevelEnv != "" { - logLevel, err := strconv.ParseUint(logLevelEnv, 10, 32) - if err != nil || logLevel > uint64(log.LvlTrace) { + logLevel, err := strconv.ParseInt(logLevelEnv, 10, 32) + if err != nil || logLevel > int64(log.LevelCrit) { log.Warn("TEST_LOGLEVEL exists but out of bound, ignoring", "logLevel", logLevelEnv, "max", log.LvlTrace) } - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(logLevel)) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(slog.Level(logLevel)) + log.SetDefault(log.NewLogger(glogger)) } code := m.Run() os.Exit(code) @@ -1136,3 +1280,13 @@ func getExecNode(t *testing.T, node *arbnode.Node) *gethexec.ExecutionNode { } return gethExec } + +func logParser[T any](t *testing.T, source string, name string) func(*types.Log) *T { + parser := util.NewLogParser[T](source, name) + return func(log *types.Log) *T { + t.Helper() + event, err := parser(log) + Require(t, err, "failed to parse log") + return event + } +} diff --git a/system_tests/conditionaltx_test.go b/system_tests/conditionaltx_test.go index 438e42d37..4f800d976 100644 --- a/system_tests/conditionaltx_test.go +++ b/system_tests/conditionaltx_test.go @@ -101,7 +101,7 @@ func getOptions(address common.Address, rootHash common.Hash, slotValueMap map[c } func getFulfillableBlockTimeLimits(t *testing.T, blockNumber uint64, timestamp uint64) []*arbitrum_types.ConditionalOptions { - future := math.HexOrDecimal64(timestamp + 40) + future := math.HexOrDecimal64(timestamp + 70) past := math.HexOrDecimal64(timestamp - 1) futureBlockNumber := math.HexOrDecimal64(blockNumber + 1000) currentBlockNumber := math.HexOrDecimal64(blockNumber) @@ -202,6 +202,7 @@ func TestSendRawTransactionConditionalBasic(t *testing.T) { defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.nodeConfig.DelayedSequencer.Enable = false cleanup := builder.Build(t) defer cleanup() diff --git a/system_tests/das_test.go b/system_tests/das_test.go index 602c6da5e..593eaa1bb 100644 --- a/system_tests/das_test.go +++ b/system_tests/das_test.go @@ -7,6 +7,7 @@ import ( "context" "encoding/base64" "encoding/json" + "io" "math/big" "net" "net/http" @@ -25,6 +26,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" + "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/das" "github.com/offchainlabs/nitro/execution/gethexec" @@ -32,6 +34,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/signature" + "golang.org/x/exp/slog" ) func startLocalDASServer( @@ -58,21 +61,19 @@ func startLocalDASServer( RequestTimeout: 5 * time.Second, } - var syncFromStorageServices []*das.IterableStorageService - var syncToStorageServices []das.StorageService - storageService, lifecycleManager, err := das.CreatePersistentStorageService(ctx, &config, &syncFromStorageServices, &syncToStorageServices) + storageService, lifecycleManager, err := das.CreatePersistentStorageService(ctx, &config) defer lifecycleManager.StopAndWaitUntil(time.Second) Require(t, err) seqInboxCaller, err := bridgegen.NewSequencerInboxCaller(seqInboxAddress, l1client) Require(t, err) - privKey, err := config.Key.BLSPrivKey() + daWriter, err := das.NewSignAfterStoreDASWriter(ctx, config, storageService) Require(t, err) - daWriter, err := das.NewSignAfterStoreDASWriterWithSeqInboxCaller(privKey, seqInboxCaller, storageService, "") + signatureVerifier, err := das.NewSignatureVerifierWithSeqInboxCaller(seqInboxCaller, "") Require(t, err) rpcLis, err := net.Listen("tcp", "localhost:0") Require(t, err) - rpcServer, err := das.StartDASRPCServerOnListener(ctx, rpcLis, genericconf.HTTPServerTimeoutConfigDefault, storageService, daWriter, storageService) + rpcServer, err := das.StartDASRPCServerOnListener(ctx, rpcLis, genericconf.HTTPServerTimeoutConfigDefault, genericconf.HTTPServerBodyLimitDefault, storageService, daWriter, storageService, signatureVerifier) Require(t, err) restLis, err := net.Listen("tcp", "localhost:0") Require(t, err) @@ -97,9 +98,10 @@ func aggConfigForBackend(t *testing.T, backendConfig das.BackendConfig) das.Aggr backendsJsonByte, err := json.Marshal([]das.BackendConfig{backendConfig}) Require(t, err) return das.AggregatorConfig{ - Enable: true, - AssumedHonest: 1, - Backends: string(backendsJsonByte), + Enable: true, + AssumedHonest: 1, + Backends: string(backendsJsonByte), + MaxStoreChunkBodySize: 512 * 1024, } } @@ -175,10 +177,10 @@ func TestDASRekey(t *testing.T) { l2stackA, err := node.New(stackConfig) Require(t, err) - l2chainDb, err := l2stackA.OpenDatabase("chaindb", 0, 0, "", false) + l2chainDb, err := l2stackA.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) Require(t, err) - l2arbDb, err := l2stackA.OpenDatabase("arbitrumdata", 0, 0, "", false) + l2arbDb, err := l2stackA.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) l2blockchain, err := gethexec.GetBlockChain(l2chainDb, nil, chainConfig, gethexec.ConfigDefaultTest().TxLookupLimit) @@ -276,12 +278,12 @@ func TestDASComplexConfigAndRestMirror(t *testing.T) { // L1NodeURL: normally we would have to set this but we are passing in the already constructed client and addresses to the factory } - daReader, daWriter, daHealthChecker, lifecycleManager, err := das.CreateDAComponentsForDaserver(ctx, &serverConfig, l1Reader, &addresses.SequencerInbox) + daReader, daWriter, signatureVerifier, daHealthChecker, lifecycleManager, err := das.CreateDAComponentsForDaserver(ctx, &serverConfig, l1Reader, &addresses.SequencerInbox) Require(t, err) defer lifecycleManager.StopAndWaitUntil(time.Second) rpcLis, err := net.Listen("tcp", "localhost:0") Require(t, err) - _, err = das.StartDASRPCServerOnListener(ctx, rpcLis, genericconf.HTTPServerTimeoutConfigDefault, daReader, daWriter, daHealthChecker) + _, err = das.StartDASRPCServerOnListener(ctx, rpcLis, genericconf.HTTPServerTimeoutConfigDefault, genericconf.HTTPServerBodyLimitDefault, daReader, daWriter, daHealthChecker, signatureVerifier) Require(t, err) restLis, err := net.Listen("tcp", "localhost:0") Require(t, err) @@ -356,9 +358,10 @@ func TestDASComplexConfigAndRestMirror(t *testing.T) { } func enableLogging(logLvl int) { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(logLvl)) - log.Root().SetHandler(glogger) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(slog.Level(logLvl)) + log.SetDefault(log.NewLogger(glogger)) } func initTest(t *testing.T) { diff --git a/system_tests/debug_trace_test.go b/system_tests/debug_trace_test.go new file mode 100644 index 000000000..1a83e5ad2 --- /dev/null +++ b/system_tests/debug_trace_test.go @@ -0,0 +1,168 @@ +package arbtest + +import ( + "context" + "encoding/binary" + "encoding/json" + "fmt" + "math/big" + "strings" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestDebugTraceCallForRecentBlock(t *testing.T) { + threads := 32 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig.Caching.Archive = true + cleanup := builder.Build(t) + defer cleanup() + builder.L2Info.GenerateAccount("User2") + builder.L2Info.GenerateAccount("User3") + + errors := make(chan error, threads+1) + senderDone := make(chan struct{}) + go func() { + defer close(senderDone) + for ctx.Err() == nil { + tx := builder.L2Info.PrepareTx("Owner", "User2", builder.L2Info.TransferGas, new(big.Int).Lsh(big.NewInt(1), 128), nil) + err := builder.L2.Client.SendTransaction(ctx, tx) + if ctx.Err() != nil { + return + } + if err != nil { + errors <- err + return + } + _, err = builder.L2.EnsureTxSucceeded(tx) + if ctx.Err() != nil { + return + } + if err != nil { + errors <- err + return + } + time.Sleep(10 * time.Millisecond) + } + }() + type TransactionArgs struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` + MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` + Value *hexutil.Big `json:"value"` + Nonce *hexutil.Uint64 `json:"nonce"` + SkipL1Charging *bool `json:"skipL1Charging"` + Data *hexutil.Bytes `json:"data"` + Input *hexutil.Bytes `json:"input"` + AccessList *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + } + rpcClient := builder.L2.ConsensusNode.Stack.Attach() + sometx := builder.L2Info.PrepareTx("User2", "User3", builder.L2Info.TransferGas, common.Big1, nil) + from := builder.L2Info.GetAddress("User2") + to := sometx.To() + gas := sometx.Gas() + maxFeePerGas := sometx.GasFeeCap() + value := sometx.Value() + nonce := sometx.Nonce() + data := sometx.Data() + txargs := TransactionArgs{ + From: &from, + To: to, + Gas: (*hexutil.Uint64)(&gas), + MaxFeePerGas: (*hexutil.Big)(maxFeePerGas), + Value: (*hexutil.Big)(value), + Nonce: (*hexutil.Uint64)(&nonce), + Data: (*hexutil.Bytes)(&data), + } + db := builder.L2.ExecNode.Backend.ChainDb() + + i := 1 + var mtx sync.RWMutex + var wgTrace sync.WaitGroup + for j := 0; j < threads && ctx.Err() == nil; j++ { + wgTrace.Add(1) + go func() { + defer wgTrace.Done() + mtx.RLock() + blockNumber := i + mtx.RUnlock() + for blockNumber < 300 && ctx.Err() == nil { + var err error + prefix := make([]byte, 8) + binary.BigEndian.PutUint64(prefix, uint64(blockNumber)) + prefix = append([]byte("b"), prefix...) + it := db.NewIterator(prefix, nil) + defer it.Release() + if it.Next() { + key := it.Key() + if len(key) != len(prefix)+common.HashLength { + Fatal(t, "Wrong key length, have:", len(key), "want:", len(prefix)+common.HashLength) + } + blockHash := common.BytesToHash(key[len(prefix):]) + start := time.Now() + for ctx.Err() == nil { + var res json.RawMessage + err = rpcClient.CallContext(ctx, &res, "debug_traceCall", txargs, blockHash, nil) + if err == nil { + mtx.Lock() + if blockNumber == i { + i++ + } + mtx.Unlock() + break + } + if ctx.Err() != nil { + return + } + if !strings.Contains(err.Error(), "not currently canonical") && !strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "missing trie node") { + errors <- err + return + } + if time.Since(start) > 5*time.Second { + errors <- fmt.Errorf("timeout - failed to trace call for more then 5 seconds, block: %d, err: %w", blockNumber, err) + return + } + } + } + it.Release() + mtx.RLock() + blockNumber = i + mtx.RUnlock() + } + }() + } + traceDone := make(chan struct{}) + go func() { + wgTrace.Wait() + close(traceDone) + }() + + select { + case <-traceDone: + cancel() + case <-senderDone: + cancel() + case err := <-errors: + t.Error(err) + cancel() + } + <-traceDone + <-senderDone + close(errors) + for err := range errors { + if err != nil { + t.Error(err) + } + } +} diff --git a/system_tests/debugapi_test.go b/system_tests/debugapi_test.go index 52a6bb25c..30a2bee03 100644 --- a/system_tests/debugapi_test.go +++ b/system_tests/debugapi_test.go @@ -2,15 +2,15 @@ package arbtest import ( "context" - "github.com/ethereum/go-ethereum/eth/tracers" + "encoding/json" "testing" - "encoding/json" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" ) @@ -34,7 +34,7 @@ func TestDebugAPI(t *testing.T) { err = l2rpc.CallContext(ctx, &badBlocks, "debug_getBadBlocks") Require(t, err) - var dumpIt state.IteratorDump + var dumpIt state.Dump err = l2rpc.CallContext(ctx, &dumpIt, "debug_accountRange", rpc.LatestBlockNumber, hexutil.Bytes{}, 10, true, true, false) Require(t, err) err = l2rpc.CallContext(ctx, &dumpIt, "debug_accountRange", rpc.PendingBlockNumber, hexutil.Bytes{}, 10, true, true, false) diff --git a/system_tests/estimation_test.go b/system_tests/estimation_test.go index 6f47c14f1..e7f00ca94 100644 --- a/system_tests/estimation_test.go +++ b/system_tests/estimation_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/gasestimator" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/solgen/go/mocksgen" @@ -285,7 +286,7 @@ func TestComponentEstimate(t *testing.T) { l2Used := receipt.GasUsed - receipt.GasUsedForL1 colors.PrintMint("True ", receipt.GasUsed, " - ", receipt.GasUsedForL1, " = ", l2Used) - if l2Estimate != l2Used { + if float64(l2Estimate-l2Used) > float64(gasEstimateForL1+l2Used)*gasestimator.EstimateGasErrorRatio { Fatal(t, l2Estimate, l2Used) } } diff --git a/system_tests/eth_sync_test.go b/system_tests/eth_sync_test.go new file mode 100644 index 000000000..1f07f7c45 --- /dev/null +++ b/system_tests/eth_sync_test.go @@ -0,0 +1,81 @@ +package arbtest + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/core/types" +) + +func TestEthSyncing(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.L2Info = nil + cleanup := builder.Build(t) + defer cleanup() + + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{}) + defer cleanupB() + + // stop txstreamer so it won't feed execution messages + testClientB.ConsensusNode.TxStreamer.StopAndWait() + + countBefore, err := testClientB.ConsensusNode.TxStreamer.GetMessageCount() + Require(t, err) + + builder.L2Info.GenerateAccount("User2") + + tx := builder.L2Info.PrepareTx("Owner", "User2", builder.L2Info.TransferGas, big.NewInt(1e12), nil) + + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // give the inbox reader a bit of time to pick up the delayed message + time.Sleep(time.Millisecond * 100) + + // sending l1 messages creates l1 blocks.. make enough to get that delayed inbox message in + for i := 0; i < 30; i++ { + builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ + builder.L1Info.PrepareTx("Faucet", "User", 30000, big.NewInt(1e12), nil), + }) + } + + attempt := 0 + for { + if attempt > 30 { + Fatal(t, "2nd node didn't get tx on time") + } + Require(t, ctx.Err()) + countAfter, err := testClientB.ConsensusNode.TxStreamer.GetMessageCount() + Require(t, err) + if countAfter > countBefore { + break + } + select { + case <-time.After(time.Millisecond * 100): + case <-ctx.Done(): + } + attempt++ + } + + progress, err := testClientB.Client.SyncProgress(ctx) + Require(t, err) + if progress == nil { + Fatal(t, "eth_syncing returned nil but shouldn't have") + } + for testClientB.ConsensusNode.TxStreamer.ExecuteNextMsg(ctx, testClientB.ExecNode) { + } + progress, err = testClientB.Client.SyncProgress(ctx) + Require(t, err) + if progress != nil { + Fatal(t, "eth_syncing did not return nil but should have") + } +} diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 3ff3bfc43..4d8fbf43f 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -119,8 +119,6 @@ func TestSequencerFeePaid(t *testing.T) { } func testSequencerPriceAdjustsFrom(t *testing.T, initialEstimate uint64) { - t.Parallel() - _ = os.Mkdir("test-data", 0766) path := filepath.Join("test-data", fmt.Sprintf("testSequencerPriceAdjustsFrom%v.csv", initialEstimate)) @@ -196,10 +194,11 @@ func testSequencerPriceAdjustsFrom(t *testing.T, initialEstimate uint64) { surplus, err := arbGasInfo.GetL1PricingSurplus(callOpts) Require(t, err) - colors.PrintGrey("ArbOS updated its L1 estimate") - colors.PrintGrey(" L1 base fee ", l1Header.BaseFee) - colors.PrintGrey(" L1 estimate ", lastEstimate, " ➤ ", estimatedL1FeePerUnit, " = ", actualL1FeePerUnit) - colors.PrintGrey(" Surplus ", surplus) + // Uncomment for model updates + // colors.PrintGrey("ArbOS updated its L1 estimate") + // colors.PrintGrey(" L1 base fee ", l1Header.BaseFee) + // colors.PrintGrey(" L1 estimate ", lastEstimate, " ➤ ", estimatedL1FeePerUnit, " = ", actualL1FeePerUnit) + // colors.PrintGrey(" Surplus ", surplus) fmt.Fprintf( f, "%v, %v, %v, %v, %v, %v\n", i, l1Header.BaseFee, lastEstimate, estimatedL1FeePerUnit, actualL1FeePerUnit, surplus, diff --git a/system_tests/full_challenge_impl_test.go b/system_tests/full_challenge_impl_test.go index 2a9b51fe6..76d4c2714 100644 --- a/system_tests/full_challenge_impl_test.go +++ b/system_tests/full_challenge_impl_test.go @@ -1,10 +1,6 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -// race detection makes things slow and miss timeouts -//go:build !race -// +build !race - package arbtest import ( @@ -29,8 +25,10 @@ import ( "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" + "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbstate" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/solgen/go/challengegen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" @@ -38,6 +36,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/ospgen" "github.com/offchainlabs/nitro/solgen/go/yulgen" "github.com/offchainlabs/nitro/staker" + "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" @@ -247,10 +246,21 @@ func setupSequencerInboxStub(ctx context.Context, t *testing.T, l1Info *Blockcha return bridgeAddr, seqInbox, seqInboxAddr } +func createL2Nodes(t *testing.T, ctx context.Context, conf *arbnode.Config, chainConfig *params.ChainConfig, l1Client arbutil.L1Interface, l2info *BlockchainTestInfo, rollupAddresses *chaininfo.RollupAddresses, initMsg *arbostypes.ParsedInitMessage, txOpts *bind.TransactOpts, signer signature.DataSignerFunc, fatalErrChan chan error) (*arbnode.Node, *gethexec.ExecutionNode) { + _, stack, l2ChainDb, l2ArbDb, l2Blockchain := createL2BlockChainWithStackConfig(t, l2info, "", chainConfig, initMsg, nil, nil) + execNode, err := gethexec.CreateExecutionNode(ctx, stack, l2ChainDb, l2Blockchain, l1Client, gethexec.ConfigDefaultTest) + Require(t, err) + consensusNode, err := arbnode.CreateNode(ctx, stack, execNode, l2ArbDb, NewFetcherFromConfig(conf), chainConfig, l1Client, rollupAddresses, txOpts, txOpts, signer, fatalErrChan, big.NewInt(1337), nil) + Require(t, err) + + return consensusNode, execNode +} + func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, challengeMsgIdx int64) { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) glogger.Verbosity(log.LvlInfo) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -276,7 +286,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall } else { _, valStack = createTestValidationNode(t, ctx, &valnode.TestValidationConfig) } - configByValidationNode(t, conf, valStack) + configByValidationNode(conf, valStack) fatalErrChan := make(chan error, 10) asserterRollupAddresses, initMessage := DeployOnTestL1(t, ctx, l1Info, l1Backend, chainConfig) @@ -289,25 +299,18 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall asserterBridgeAddr, asserterSeqInbox, asserterSeqInboxAddr := setupSequencerInboxStub(ctx, t, l1Info, l1Backend, chainConfig) challengerBridgeAddr, challengerSeqInbox, challengerSeqInboxAddr := setupSequencerInboxStub(ctx, t, l1Info, l1Backend, chainConfig) - asserterL2Info, asserterL2Stack, asserterL2ChainDb, asserterL2ArbDb, asserterL2Blockchain := createL2BlockChainWithStackConfig(t, nil, "", chainConfig, initMessage, nil, nil) asserterRollupAddresses.Bridge = asserterBridgeAddr asserterRollupAddresses.SequencerInbox = asserterSeqInboxAddr - asserterExec, err := gethexec.CreateExecutionNode(ctx, asserterL2Stack, asserterL2ChainDb, asserterL2Blockchain, l1Backend, gethexec.ConfigDefaultTest) - Require(t, err) - parentChainID := big.NewInt(1337) - asserterL2, err := arbnode.CreateNode(ctx, asserterL2Stack, asserterExec, asserterL2ArbDb, NewFetcherFromConfig(conf), chainConfig, l1Backend, asserterRollupAddresses, nil, nil, nil, fatalErrChan, parentChainID, nil) - Require(t, err) - err = asserterL2.Start(ctx) + asserterL2Info := NewArbTestInfo(t, chainConfig.ChainID) + asserterL2, asserterExec := createL2Nodes(t, ctx, conf, chainConfig, l1Backend, asserterL2Info, asserterRollupAddresses, initMessage, nil, nil, fatalErrChan) + err := asserterL2.Start(ctx) Require(t, err) - challengerL2Info, challengerL2Stack, challengerL2ChainDb, challengerL2ArbDb, challengerL2Blockchain := createL2BlockChainWithStackConfig(t, nil, "", chainConfig, initMessage, nil, nil) challengerRollupAddresses := *asserterRollupAddresses challengerRollupAddresses.Bridge = challengerBridgeAddr challengerRollupAddresses.SequencerInbox = challengerSeqInboxAddr - challengerExec, err := gethexec.CreateExecutionNode(ctx, challengerL2Stack, challengerL2ChainDb, challengerL2Blockchain, l1Backend, gethexec.ConfigDefaultTest) - Require(t, err) - challengerL2, err := arbnode.CreateNode(ctx, challengerL2Stack, challengerExec, challengerL2ArbDb, NewFetcherFromConfig(conf), chainConfig, l1Backend, &challengerRollupAddresses, nil, nil, nil, fatalErrChan, parentChainID, nil) - Require(t, err) + challengerL2Info := NewArbTestInfo(t, chainConfig.ChainID) + challengerL2, challengerExec := createL2Nodes(t, ctx, conf, chainConfig, l1Backend, challengerL2Info, &challengerRollupAddresses, initMessage, nil, nil, fatalErrChan) err = challengerL2.Start(ctx) Require(t, err) @@ -346,7 +349,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall } var wasmModuleRoot common.Hash if useStubs { - wasmModuleRoot = mockWasmModuleRoot + wasmModuleRoot = mockWasmModuleRoots[0] } else { wasmModuleRoot = locator.LatestWasmModuleRoot() if (wasmModuleRoot == common.Hash{}) { @@ -395,7 +398,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall confirmLatestBlock(ctx, t, l1Info, l1Backend) - asserterValidator, err := staker.NewStatelessBlockValidator(asserterL2.InboxReader, asserterL2.InboxTracker, asserterL2.TxStreamer, asserterExec.Recorder, asserterL2ArbDb, nil, nil, nil, StaticFetcherFrom(t, &conf.BlockValidator), valStack) + asserterValidator, err := staker.NewStatelessBlockValidator(asserterL2.InboxReader, asserterL2.InboxTracker, asserterL2.TxStreamer, asserterExec.Recorder, asserterL2.ArbDB, nil, StaticFetcherFrom(t, &conf.BlockValidator), valStack) if err != nil { Fatal(t, err) } @@ -412,7 +415,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall if err != nil { Fatal(t, err) } - challengerValidator, err := staker.NewStatelessBlockValidator(challengerL2.InboxReader, challengerL2.InboxTracker, challengerL2.TxStreamer, challengerExec.Recorder, challengerL2ArbDb, nil, nil, nil, StaticFetcherFrom(t, &conf.BlockValidator), valStack) + challengerValidator, err := staker.NewStatelessBlockValidator(challengerL2.InboxReader, challengerL2.InboxTracker, challengerL2.TxStreamer, challengerExec.Recorder, challengerL2.ArbDB, nil, StaticFetcherFrom(t, &conf.BlockValidator), valStack) if err != nil { Fatal(t, err) } @@ -500,17 +503,3 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall Fatal(t, "challenge timed out without winner") } - -func TestMockChallengeManagerAsserterIncorrect(t *testing.T) { - t.Parallel() - for i := int64(1); i <= makeBatch_MsgsPerBatch*3; i++ { - RunChallengeTest(t, false, true, i) - } -} - -func TestMockChallengeManagerAsserterCorrect(t *testing.T) { - t.Parallel() - for i := int64(1); i <= makeBatch_MsgsPerBatch*3; i++ { - RunChallengeTest(t, true, true, i) - } -} diff --git a/system_tests/full_challenge_mock_test.go b/system_tests/full_challenge_mock_test.go new file mode 100644 index 000000000..d32c2b40a --- /dev/null +++ b/system_tests/full_challenge_mock_test.go @@ -0,0 +1,21 @@ +// race detection makes things slow and miss timeouts +//go:build !race +// +build !race + +package arbtest + +import "testing" + +func TestMockChallengeManagerAsserterIncorrect(t *testing.T) { + t.Parallel() + for i := int64(1); i <= makeBatch_MsgsPerBatch*3; i++ { + RunChallengeTest(t, false, true, i) + } +} + +func TestMockChallengeManagerAsserterCorrect(t *testing.T) { + t.Parallel() + for i := int64(1); i <= makeBatch_MsgsPerBatch*3; i++ { + RunChallengeTest(t, true, true, i) + } +} diff --git a/system_tests/full_challenge_test.go b/system_tests/full_challenge_test.go index a960e7f64..d15ee83d1 100644 --- a/system_tests/full_challenge_test.go +++ b/system_tests/full_challenge_test.go @@ -1,18 +1,12 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE //go:build challengetest // +build challengetest -// -// Copyright 2021-2022, Offchain Labs, Inc. All rights reserved. -// - package arbtest -import ( - "testing" -) +import "testing" func TestChallengeManagerFullAsserterIncorrect(t *testing.T) { t.Parallel() diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go index 3424a58e9..b692af6e3 100644 --- a/system_tests/nodeinterface_test.go +++ b/system_tests/nodeinterface_test.go @@ -1,6 +1,10 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +// race detection makes things slow and miss timeouts +//go:build !race +// +build !race + package arbtest import ( @@ -11,10 +15,82 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" ) +func TestFindBatch(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l1Info := NewL1TestInfo(t) + initialBalance := new(big.Int).Lsh(big.NewInt(1), 200) + l1Info.GenerateGenesisAccount("deployer", initialBalance) + l1Info.GenerateGenesisAccount("asserter", initialBalance) + l1Info.GenerateGenesisAccount("challenger", initialBalance) + l1Info.GenerateGenesisAccount("sequencer", initialBalance) + + l1Info, l1Backend, _, _ := createTestL1BlockChain(t, l1Info) + conf := arbnode.ConfigDefaultL1Test() + conf.BlockValidator.Enable = false + conf.BatchPoster.Enable = false + + chainConfig := params.ArbitrumDevTestChainConfig() + fatalErrChan := make(chan error, 10) + rollupAddresses, initMsg := DeployOnTestL1(t, ctx, l1Info, l1Backend, chainConfig) + + bridgeAddr, seqInbox, seqInboxAddr := setupSequencerInboxStub(ctx, t, l1Info, l1Backend, chainConfig) + + callOpts := bind.CallOpts{Context: ctx} + + rollupAddresses.Bridge = bridgeAddr + rollupAddresses.SequencerInbox = seqInboxAddr + l2Info := NewArbTestInfo(t, chainConfig.ChainID) + consensus, _ := createL2Nodes(t, ctx, conf, chainConfig, l1Backend, l2Info, rollupAddresses, initMsg, nil, nil, fatalErrChan) + err := consensus.Start(ctx) + Require(t, err) + + l2Client := ClientForStack(t, consensus.Stack) + nodeInterface, err := node_interfacegen.NewNodeInterface(types.NodeInterfaceAddress, l2Client) + Require(t, err) + sequencerTxOpts := l1Info.GetDefaultTransactOpts("sequencer", ctx) + + l2Info.GenerateAccount("Destination") + makeBatch(t, consensus, l2Info, l1Backend, &sequencerTxOpts, seqInbox, seqInboxAddr, -1) + makeBatch(t, consensus, l2Info, l1Backend, &sequencerTxOpts, seqInbox, seqInboxAddr, -1) + makeBatch(t, consensus, l2Info, l1Backend, &sequencerTxOpts, seqInbox, seqInboxAddr, -1) + + for blockNum := uint64(0); blockNum < uint64(makeBatch_MsgsPerBatch)*3; blockNum++ { + gotBatchNum, err := nodeInterface.FindBatchContainingBlock(&callOpts, blockNum) + Require(t, err) + expBatchNum := uint64(0) + if blockNum > 0 { + expBatchNum = 1 + (blockNum-1)/uint64(makeBatch_MsgsPerBatch) + } + if expBatchNum != gotBatchNum { + Fatal(t, "wrong result from findBatchContainingBlock. blocknum ", blockNum, " expected ", expBatchNum, " got ", gotBatchNum) + } + batchL1Block, err := consensus.InboxTracker.GetBatchParentChainBlock(gotBatchNum) + Require(t, err) + blockHeader, err := l2Client.HeaderByNumber(ctx, new(big.Int).SetUint64(blockNum)) + Require(t, err) + blockHash := blockHeader.Hash() + + minCurrentL1Block, err := l1Backend.BlockNumber(ctx) + Require(t, err) + gotConfirmations, err := nodeInterface.GetL1Confirmations(&callOpts, blockHash) + Require(t, err) + maxCurrentL1Block, err := l1Backend.BlockNumber(ctx) + Require(t, err) + + if gotConfirmations > (maxCurrentL1Block-batchL1Block) || gotConfirmations < (minCurrentL1Block-batchL1Block) { + Fatal(t, "wrong number of confirmations. got ", gotConfirmations) + } + } +} + func TestL2BlockRangeForL1(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) diff --git a/system_tests/outbox_test.go b/system_tests/outbox_test.go index d0ca0ccda..739d756a3 100644 --- a/system_tests/outbox_test.go +++ b/system_tests/outbox_test.go @@ -15,7 +15,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/gethhook" "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" @@ -23,6 +25,31 @@ import ( "github.com/offchainlabs/nitro/util/merkletree" ) +func TestP256VerifyEnabled(t *testing.T) { + gethhook.RequireHookedGeth() + for _, tc := range []struct { + stylusEnabled bool + wantP256Verify bool + }{ + { + stylusEnabled: false, + wantP256Verify: false, + }, + { + stylusEnabled: true, + wantP256Verify: true, + }, + } { + got := false + for _, a := range vm.ActivePrecompiles(params.Rules{IsStylus: tc.stylusEnabled}) { + got = got || (a == common.BytesToAddress([]byte{0x01, 0x00})) + } + if got != tc.wantP256Verify { + t.Errorf("Got P256Verify enabled: %t, want: %t", got, tc.wantP256Verify) + } + } +} + func TestOutboxProofs(t *testing.T) { t.Parallel() gethhook.RequireHookedGeth() diff --git a/system_tests/precompile_test.go b/system_tests/precompile_test.go index 0ad0f8f1e..9e829124e 100644 --- a/system_tests/precompile_test.go +++ b/system_tests/precompile_test.go @@ -68,7 +68,7 @@ func TestCustomSolidityErrors(t *testing.T) { Fatal(t, "customRevert call should have errored") } observedMessage := customError.Error() - expectedError := "Custom(1024, This spider family wards off bugs: /\\oo/\\ //\\(oo)/\\ /\\oo/\\, true)" + expectedError := "Custom(1024, This spider family wards off bugs: /\\oo/\\ //\\(oo)//\\ /\\oo/\\, true)" // The first error is server side. The second error is client side ABI decoding. expectedMessage := fmt.Sprintf("execution reverted: error %v: %v", expectedError, expectedError) if observedMessage != expectedMessage { diff --git a/system_tests/program_norace_test.go b/system_tests/program_norace_test.go new file mode 100644 index 000000000..56b204671 --- /dev/null +++ b/system_tests/program_norace_test.go @@ -0,0 +1,211 @@ +// race detection makes things slow and miss timeouts +//go:build !race +// +build !race + +package arbtest + +import ( + "encoding/binary" + "encoding/json" + "math/big" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/colors" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func blockIsEmpty(block *types.Block) bool { + for _, tx := range block.Transactions() { + if tx.Type() != types.ArbitrumInternalTxType { + return false + } + } + return true +} + +func nonEmptyBlockHeight(t *testing.T, builder *NodeBuilder) uint64 { + latestBlock, err := builder.L2.Client.BlockByNumber(builder.ctx, nil) + Require(t, err) + for blockIsEmpty(latestBlock) { + prior := arbmath.BigSubByUint(latestBlock.Number(), 1) + latestBlock, err = builder.L2.Client.BlockByNumber(builder.ctx, prior) + Require(t, err) + } + return latestBlock.NumberU64() +} + +// used in program test +func validateBlocks( + t *testing.T, start uint64, jit bool, builder *NodeBuilder, +) { + t.Helper() + if jit || start == 0 { + start = 1 + } + + blockHeight := nonEmptyBlockHeight(t, builder) + blocks := []uint64{} + for i := start; i <= blockHeight; i++ { + blocks = append(blocks, i) + } + validateBlockRange(t, blocks, jit, builder) +} + +// used in program test +func validateBlockRange( + t *testing.T, blocks []uint64, jit bool, + builder *NodeBuilder, +) { + ctx := builder.ctx + + // validate everything + if jit { + blockHeight := nonEmptyBlockHeight(t, builder) + blocks = []uint64{} + for i := uint64(1); i <= blockHeight; i++ { + blocks = append(blocks, i) + } + } + + waitForSequencer(t, builder, arbmath.MaxInt(blocks...)) + + success := true + wasmModuleRoot := currentRootModule(t) + for _, block := range blocks { + // no classic data, so block numbers are message indicies + inboxPos := arbutil.MessageIndex(block) + + now := time.Now() + correct, _, err := builder.L2.ConsensusNode.StatelessBlockValidator.ValidateResult( + ctx, inboxPos, false, wasmModuleRoot, + ) + Require(t, err, "block", block) + passed := formatTime(time.Since(now)) + if correct { + colors.PrintMint("yay!! we validated block ", block, " in ", passed) + } else { + colors.PrintRed("failed to validate block ", block, " in ", passed) + } + success = success && correct + } + if !success { + Fatal(t) + } +} + +func TestProgramEvmData(t *testing.T) { + t.Parallel() + testEvmData(t, true) +} + +func testEvmData(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + evmDataAddr := deployWasm(t, ctx, auth, l2client, rustFile("evm-data")) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + burnArbGas, _ := util.NewCallParser(precompilesgen.ArbosTestABI, "burnArbGas") + + _, tx, mock, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + + evmDataGas := uint64(1000000000) + gasToBurn := uint64(1000000) + callBurnData, err := burnArbGas(new(big.Int).SetUint64(gasToBurn)) + Require(t, err) + fundedAddr := l2info.Accounts["Faucet"].Address + ethPrecompile := common.BigToAddress(big.NewInt(1)) + arbTestAddress := types.ArbosTestAddress + + evmDataData := []byte{} + evmDataData = append(evmDataData, fundedAddr.Bytes()...) + evmDataData = append(evmDataData, ethPrecompile.Bytes()...) + evmDataData = append(evmDataData, arbTestAddress.Bytes()...) + evmDataData = append(evmDataData, evmDataAddr.Bytes()...) + evmDataData = append(evmDataData, callBurnData...) + opts := bind.CallOpts{ + From: testhelpers.RandomAddress(), + } + + result, err := mock.StaticcallEvmData(&opts, evmDataAddr, fundedAddr, evmDataGas, evmDataData) + Require(t, err) + + advance := func(count int, name string) []byte { + t.Helper() + if len(result) < count { + Fatal(t, "not enough data left", name, count, len(result)) + } + data := result[:count] + result = result[count:] + return data + } + getU32 := func(name string) uint32 { + t.Helper() + return binary.BigEndian.Uint32(advance(4, name)) + } + getU64 := func(name string) uint64 { + t.Helper() + return binary.BigEndian.Uint64(advance(8, name)) + } + + inkPrice := uint64(getU32("ink price")) + gasLeftBefore := getU64("gas left before") + inkLeftBefore := getU64("ink left before") + gasLeftAfter := getU64("gas left after") + inkLeftAfter := getU64("ink left after") + + gasUsed := gasLeftBefore - gasLeftAfter + calculatedGasUsed := (inkLeftBefore - inkLeftAfter) / inkPrice + + // Should be within 1 gas + if !arbmath.Within(gasUsed, calculatedGasUsed, 1) { + Fatal(t, "gas and ink converted to gas don't match", gasUsed, calculatedGasUsed, inkPrice) + } + + tx = l2info.PrepareTxTo("Owner", &evmDataAddr, evmDataGas, nil, evmDataData) + ensure(tx, l2client.SendTransaction(ctx, tx)) + + // test hostio tracing + js := `{ + "hostio": function(info) { this.names.push(info.name); }, + "result": function() { return this.names; }, + "fault": function() { return this.names; }, + names: [] + }` + var trace json.RawMessage + traceConfig := &tracers.TraceConfig{ + Tracer: &js, + } + rpc := l2client.Client() + err = rpc.CallContext(ctx, &trace, "debug_traceTransaction", tx.Hash(), traceConfig) + Require(t, err) + + for _, item := range []string{"user_entrypoint", "read_args", "write_result", "user_returned"} { + if !strings.Contains(string(trace), item) { + Fatal(t, "tracer missing hostio ", item, " ", trace) + } + } + colors.PrintGrey("trace: ", string(trace)) + + validateBlocks(t, 1, jit, builder) +} diff --git a/system_tests/program_race_test.go b/system_tests/program_race_test.go new file mode 100644 index 000000000..78507934d --- /dev/null +++ b/system_tests/program_race_test.go @@ -0,0 +1,23 @@ +//go:build race +// +build race + +// when running with race detection - skip block validation + +package arbtest + +import ( + "testing" +) + +// used in program test +func validateBlocks( + t *testing.T, start uint64, jit bool, builder *NodeBuilder, +) { +} + +// used in program test +func validateBlockRange( + t *testing.T, blocks []uint64, jit bool, + builder *NodeBuilder, +) { +} diff --git a/system_tests/program_recursive_test.go b/system_tests/program_recursive_test.go new file mode 100644 index 000000000..d4cab510d --- /dev/null +++ b/system_tests/program_recursive_test.go @@ -0,0 +1,195 @@ +package arbtest + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +type multiCallRecurse struct { + Name string + opcode vm.OpCode +} + +func printRecurse(recurse []multiCallRecurse) string { + result := "" + for _, contract := range recurse { + result = result + "(" + contract.Name + "," + contract.opcode.String() + ")" + } + return result +} + +func testProgramRecursiveCall(t *testing.T, builder *NodeBuilder, slotVals map[string]common.Hash, rander *testhelpers.PseudoRandomDataSource, recurse []multiCallRecurse) uint64 { + ctx := builder.ctx + slot := common.HexToHash("0x11223344556677889900aabbccddeeff") + val := common.Hash{} + args := []byte{} + if recurse[0].opcode == vm.SSTORE { + // send event from storage on sstore + val = rander.GetHash() + args = append([]byte{0x1, 0, 0, 0, 65, 0x18}, slot[:]...) + args = append(args, val[:]...) + } else if recurse[0].opcode == vm.SLOAD { + args = append([]byte{0x1, 0, 0, 0, 33, 0x11}, slot[:]...) + } else { + t.Fatal("first level must be sload or sstore") + } + shouldSucceed := true + delegateChangesStorageDest := true + storageDest := recurse[0].Name + for i := 1; i < len(recurse); i++ { + call := recurse[i] + prev := recurse[i-1] + args = argsForMulticall(call.opcode, builder.L2Info.GetAddress(prev.Name), nil, args) + if call.opcode == vm.STATICCALL && recurse[0].opcode == vm.SSTORE { + shouldSucceed = false + } + if delegateChangesStorageDest && call.opcode == vm.DELEGATECALL { + storageDest = call.Name + } else { + delegateChangesStorageDest = false + } + } + if recurse[0].opcode == vm.SLOAD { + // send event from caller on sload + args[5] = args[5] | 0x8 + } + multiCaller, err := mocksgen.NewMultiCallTest(builder.L2Info.GetAddress(recurse[len(recurse)-1].Name), builder.L2.Client) + Require(t, err) + ownerTransact := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + ownerTransact.GasLimit = 10000000 + tx, err := multiCaller.Fallback(&ownerTransact, args) + Require(t, err) + receipt, err := WaitForTx(ctx, builder.L2.Client, tx.Hash(), time.Second*3) + Require(t, err) + + if shouldSucceed { + if receipt.Status != types.ReceiptStatusSuccessful { + log.Error("error when shouldn't", "case", printRecurse(recurse)) + Fatal(t, arbutil.DetailTxError(ctx, builder.L2.Client, tx, receipt)) + } + if len(receipt.Logs) != 1 { + Fatal(t, "incorrect number of logs: ", len(receipt.Logs)) + } + if recurse[0].opcode == vm.SSTORE { + slotVals[storageDest] = val + storageEvt, err := multiCaller.ParseStorage(*receipt.Logs[0]) + Require(t, err) + gotData := common.BytesToHash(storageEvt.Data[:]) + gotSlot := common.BytesToHash(storageEvt.Slot[:]) + if gotData != val || gotSlot != slot || storageEvt.Write != (recurse[0].opcode == vm.SSTORE) { + Fatal(t, "unexpected event", gotData, val, gotSlot, slot, storageEvt.Write, recurse[0].opcode) + } + } else { + calledEvt, err := multiCaller.ParseCalled(*receipt.Logs[0]) + Require(t, err) + gotData := common.BytesToHash(calledEvt.ReturnData) + if gotData != slotVals[storageDest] { + Fatal(t, "unexpected event", gotData, val, slotVals[storageDest]) + } + } + } else if receipt.Status == types.ReceiptStatusSuccessful { + Fatal(t, "should have failed") + } + for contract, expected := range slotVals { + found, err := builder.L2.Client.StorageAt(ctx, builder.L2Info.GetAddress(contract), slot, receipt.BlockNumber) + Require(t, err) + foundHash := common.BytesToHash(found) + if expected != foundHash { + Fatal(t, "contract", contract, "expected", expected, "found", foundHash) + } + } + return receipt.BlockNumber.Uint64() +} + +func testProgramResursiveCalls(t *testing.T, tests [][]multiCallRecurse, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + // set-up contracts + callsAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + builder.L2Info.SetContract("multicall-rust", callsAddr) + multiCallWasm, _ := readWasmFile(t, rustFile("multicall")) + auth.GasLimit = 32000000 // skip gas estimation + multicallB := deployContract(t, ctx, auth, l2client, multiCallWasm) + builder.L2Info.SetContract("multicall-rust-b", multicallB) + multiAddr, tx, _, err := mocksgen.DeployMultiCallTest(&auth, builder.L2.Client) + builder.L2Info.SetContract("multicall-evm", multiAddr) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, builder.L2.Client, tx) + Require(t, err) + slotVals := make(map[string]common.Hash) + rander := testhelpers.NewPseudoRandomDataSource(t, 0) + + // set-up validator + validatorConfig := arbnode.ConfigDefaultL1NonSequencerTest() + validatorConfig.BlockValidator.Enable = true + AddDefaultValNode(t, ctx, validatorConfig, jit, "") + valClient, valCleanup := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) + defer valCleanup() + + // store initial values + for _, contract := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + storeRecure := []multiCallRecurse{ + { + Name: contract, + opcode: vm.SSTORE, + }, + } + testProgramRecursiveCall(t, builder, slotVals, rander, storeRecure) + } + + // execute transactions + blockNum := uint64(0) + for { + item := int(rander.GetUint64()/4) % len(tests) + blockNum = testProgramRecursiveCall(t, builder, slotVals, rander, tests[item]) + tests[item] = tests[len(tests)-1] + tests = tests[:len(tests)-1] + if len(tests)%100 == 0 { + log.Error("running transactions..", "blockNum", blockNum, "remaining", len(tests)) + } + if len(tests) == 0 { + break + } + } + + // wait for validation + for { + got := valClient.ConsensusNode.BlockValidator.WaitForPos(t, ctx, arbutil.MessageIndex(blockNum), time.Second*10) + if got { + break + } + log.Error("validating blocks..", "waiting for", blockNum, "validated", valClient.ConsensusNode.BlockValidator.GetValidated()) + } +} + +func TestProgramCallSimple(t *testing.T) { + tests := [][]multiCallRecurse{ + { + { + Name: "multicall-rust", + opcode: vm.SLOAD, + }, + { + Name: "multicall-rust", + opcode: vm.STATICCALL, + }, + { + Name: "multicall-rust", + opcode: vm.DELEGATECALL, + }, + }, + } + testProgramResursiveCalls(t, tests, true) +} diff --git a/system_tests/program_test.go b/system_tests/program_test.go new file mode 100644 index 000000000..d8d9e05aa --- /dev/null +++ b/system_tests/program_test.go @@ -0,0 +1,1658 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package arbtest + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "math/big" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbos/programs" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + pgen "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/colors" + "github.com/offchainlabs/nitro/util/testhelpers" + "github.com/offchainlabs/nitro/validator/valnode" + "github.com/wasmerio/wasmer-go/wasmer" +) + +var oneEth = arbmath.UintToBig(1e18) + +func TestProgramKeccak(t *testing.T) { + t.Parallel() + keccakTest(t, true) +} + +func keccakTest(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + programAddress := deployWasm(t, ctx, auth, l2client, rustFile("keccak")) + + wasm, _ := readWasmFile(t, rustFile("keccak")) + otherAddressSameCode := deployContract(t, ctx, auth, l2client, wasm) + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + + colors.PrintBlue("program deployed to ", programAddress.Hex()) + timed(t, "activate same code", func() { + if _, err := arbWasm.ActivateProgram(&auth, otherAddressSameCode); err == nil || !strings.Contains(err.Error(), "ProgramUpToDate") { + Fatal(t, "activate should have failed with ProgramUpToDate", err) + } + }) + + if programAddress == otherAddressSameCode { + Fatal(t, "expected to deploy at two separate program addresses") + } + + stylusVersion, err := arbWasm.StylusVersion(nil) + Require(t, err) + statedb, err := builder.L2.ExecNode.Backend.ArbInterface().BlockChain().State() + Require(t, err) + codehashVersion, err := arbWasm.CodehashVersion(nil, statedb.GetCodeHash(programAddress)) + Require(t, err) + if codehashVersion != stylusVersion || stylusVersion == 0 { + Fatal(t, "unexpected versions", stylusVersion, codehashVersion) + } + programVersion, err := arbWasm.ProgramVersion(nil, programAddress) + Require(t, err) + if programVersion != stylusVersion || stylusVersion == 0 { + Fatal(t, "unexpected versions", stylusVersion, programVersion) + } + otherVersion, err := arbWasm.ProgramVersion(nil, otherAddressSameCode) + Require(t, err) + if otherVersion != programVersion { + Fatal(t, "mismatched versions", stylusVersion, programVersion) + } + + preimage := []byte("°º¤ø,¸,ø¤°º¤ø,¸,ø¤°º¤ø,¸ nyan nyan ~=[,,_,,]:3 nyan nyan") + correct := crypto.Keccak256Hash(preimage) + + args := []byte{0x01} // keccak the preimage once + args = append(args, preimage...) + + timed(t, "execute", func() { + result := sendContractCall(t, ctx, programAddress, l2client, args) + if len(result) != 32 { + Fatal(t, "unexpected return result: ", "result", result) + } + hash := common.BytesToHash(result) + if hash != correct { + Fatal(t, "computed hash mismatch", hash, correct) + } + colors.PrintGrey("keccak(x) = ", hash) + }) + timed(t, "execute same code, different address", func() { + result := sendContractCall(t, ctx, otherAddressSameCode, l2client, args) + if len(result) != 32 { + Fatal(t, "unexpected return result: ", "result", result) + } + hash := common.BytesToHash(result) + if hash != correct { + Fatal(t, "computed hash mismatch", hash, correct) + } + colors.PrintGrey("keccak(x) = ", hash) + }) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + // do a mutating call for proving's sake + _, tx, mock, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + ensure(mock.CallKeccak(&auth, programAddress, args)) + ensure(mock.CallKeccak(&auth, otherAddressSameCode, args)) + + validateBlocks(t, 1, jit, builder) +} + +func TestProgramActivateTwice(t *testing.T) { + t.Parallel() + testActivateTwice(t, true) +} + +func testActivateTwice(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, l2client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&auth, 1)) + + wasm, _ := readWasmFile(t, rustFile("keccak")) + keccakA := deployContract(t, ctx, auth, l2client, wasm) + keccakB := deployContract(t, ctx, auth, l2client, wasm) + + colors.PrintBlue("keccak program A deployed to ", keccakA) + colors.PrintBlue("keccak program B deployed to ", keccakB) + + multiAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + preimage := []byte("it's time to du-du-du-du d-d-d-d-d-d-d de-duplicate") + + keccakArgs := []byte{0x01} // keccak the preimage once + keccakArgs = append(keccakArgs, preimage...) + + checkReverts := func() { + msg := ethereum.CallMsg{ + To: &keccakA, + Data: keccakArgs, + } + _, err = l2client.CallContract(ctx, msg, nil) + if err == nil || !strings.Contains(err.Error(), "ProgramNotActivated") { + Fatal(t, "call should have failed with ProgramNotActivated") + } + + // execute onchain for proving's sake + tx := l2info.PrepareTxTo("Owner", &keccakA, 1e9, nil, keccakArgs) + Require(t, l2client.SendTransaction(ctx, tx)) + EnsureTxFailed(t, ctx, l2client, tx) + } + + // Calling the contract pre-activation should fail. + checkReverts() + + // mechanisms for creating calldata + activateProgram, _ := util.NewCallParser(pgen.ArbWasmABI, "activateProgram") + legacyError, _ := util.NewCallParser(pgen.ArbDebugABI, "legacyError") + callKeccak, _ := util.NewCallParser(mocksgen.ProgramTestABI, "callKeccak") + pack := func(data []byte, err error) []byte { + Require(t, err) + return data + } + mockAddr, tx, _, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + + // Successfully activate, but then revert + args := argsForMulticall(vm.CALL, types.ArbWasmAddress, nil, pack(activateProgram(keccakA))) + args = multicallAppend(args, vm.CALL, types.ArbDebugAddress, pack(legacyError())) + + tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, oneEth, args) + Require(t, l2client.SendTransaction(ctx, tx)) + EnsureTxFailed(t, ctx, l2client, tx) + + // Ensure the revert also reverted keccak's activation + checkReverts() + + // Activate keccak program A, then call into B, which should succeed due to being the same codehash + args = argsForMulticall(vm.CALL, types.ArbWasmAddress, oneEth, pack(activateProgram(keccakA))) + args = multicallAppend(args, vm.CALL, mockAddr, pack(callKeccak(keccakB, keccakArgs))) + + tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, oneEth, args) + ensure(tx, l2client.SendTransaction(ctx, tx)) + + validateBlocks(t, 7, jit, builder) +} + +func TestProgramErrors(t *testing.T) { + t.Parallel() + errorTest(t, true) +} + +func errorTest(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + programAddress := deployWasm(t, ctx, auth, l2client, rustFile("fallible")) + multiAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + + // ensure tx passes + tx := l2info.PrepareTxTo("Owner", &programAddress, l2info.TransferGas, nil, []byte{0x01}) + Require(t, l2client.SendTransaction(ctx, tx)) + _, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + + // ensure tx fails + tx = l2info.PrepareTxTo("Owner", &programAddress, l2info.TransferGas, nil, []byte{0x00}) + Require(t, l2client.SendTransaction(ctx, tx)) + receipt, err := WaitForTx(ctx, l2client, tx.Hash(), 5*time.Second) + Require(t, err) + if receipt.Status != types.ReceiptStatusFailed { + Fatal(t, "call should have failed") + } + + // ensure tx recovery is correct after failing in a deeply nested call + args := []byte{} + for i := 0; i < 32; i++ { + args = argsForMulticall(vm.CALL, multiAddr, nil, args) + } + tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, nil, args) + Require(t, l2client.SendTransaction(ctx, tx)) + EnsureTxFailed(t, ctx, l2client, tx) + + validateBlocks(t, 7, jit, builder) +} + +func TestProgramStorage(t *testing.T) { + t.Parallel() + storageTest(t, true) +} + +func storageTest(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + programAddress := deployWasm(t, ctx, auth, l2client, rustFile("storage")) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + key := testhelpers.RandomHash() + value := testhelpers.RandomHash() + tx := l2info.PrepareTxTo("Owner", &programAddress, l2info.TransferGas, nil, argsForStorageWrite(key, value)) + ensure(tx, l2client.SendTransaction(ctx, tx)) + assertStorageAt(t, ctx, l2client, programAddress, key, value) + + validateBlocks(t, 2, jit, builder) +} + +func TestProgramTransientStorage(t *testing.T) { + t.Parallel() + transientStorageTest(t, true) +} + +func transientStorageTest(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + storage := deployWasm(t, ctx, auth, l2client, rustFile("storage")) + multicall := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + + trans := func(args []byte) []byte { + args[0] += 2 + return args + } + + zero := common.Hash{} + keys := []common.Hash{} + values := []common.Hash{} + stored := []common.Hash{} + args := argsForMulticall(vm.CALL, storage, nil, trans(argsForStorageWrite(zero, zero))) + + for i := 0; i < 8; i++ { + keys = append(keys, testhelpers.RandomHash()) + values = append(values, testhelpers.RandomHash()) + if i%2 == 0 { + args = multicallAppend(args, vm.CALL, storage, argsForStorageWrite(keys[i], values[i])) + args = multicallAppend(args, vm.CALL, storage, argsForStorageRead(keys[i])) + stored = append(stored, values[i]) + } else { + args = multicallAppend(args, vm.CALL, storage, trans(argsForStorageWrite(keys[i], values[i]))) + args = multicallAppend(args, vm.CALL, storage, trans(argsForStorageRead(keys[i]))) + stored = append(stored, zero) + } + } + + // do an onchain call + tx := l2info.PrepareTxTo("Owner", &multicall, l2info.TransferGas, nil, args) + Require(t, l2client.SendTransaction(ctx, tx)) + _, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + + // do an equivalent eth_call + msg := ethereum.CallMsg{ + To: &multicall, + Data: args, + } + outs, err := l2client.CallContract(ctx, msg, nil) + Require(t, err) + + for i, key := range keys { + offset := i * 32 + value := common.BytesToHash(outs[offset : offset+32]) + if values[i] != value { + Fatal(t, "unexpected value in transient storage", i, values[i], value) + } + assertStorageAt(t, ctx, l2client, storage, key, stored[i]) + assertStorageAt(t, ctx, l2client, multicall, key, zero) + } + + validateBlocks(t, 7, jit, builder) +} + +func TestProgramMath(t *testing.T) { + t.Parallel() + fastMathTest(t, true) +} + +func fastMathTest(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + program := deployWasm(t, ctx, auth, l2client, rustFile("math")) + + _, tx, mock, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + ensure(mock.MathTest(&auth, program)) + + validateBlocks(t, 6, jit, builder) +} + +func TestProgramCalls(t *testing.T) { + t.Parallel() + testCalls(t, true) +} + +func testCalls(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + callsAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + expectFailure := func(to common.Address, data []byte, errMsg string) { + t.Helper() + msg := ethereum.CallMsg{ + To: &to, + Data: data, + } + _, err := l2client.CallContract(ctx, msg, nil) + if err == nil { + Fatal(t, "call should have failed with", errMsg) + } + expected := fmt.Sprintf("execution reverted%v", errMsg) + if err.Error() != expected { + Fatal(t, "wrong error", err.Error(), " ", expected) + } + + // execute onchain for proving's sake + tx := l2info.PrepareTxTo("Owner", &callsAddr, 1e9, nil, data) + Require(t, l2client.SendTransaction(ctx, tx)) + EnsureTxFailed(t, ctx, l2client, tx) + } + + storeAddr := deployWasm(t, ctx, auth, l2client, rustFile("storage")) + keccakAddr := deployWasm(t, ctx, auth, l2client, rustFile("keccak")) + mockAddr, tx, _, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + + colors.PrintGrey("multicall.wasm ", callsAddr) + colors.PrintGrey("storage.wasm ", storeAddr) + colors.PrintGrey("keccak.wasm ", keccakAddr) + colors.PrintGrey("mock.evm ", mockAddr) + + kinds := make(map[vm.OpCode]byte) + kinds[vm.CALL] = 0x00 + kinds[vm.DELEGATECALL] = 0x01 + kinds[vm.STATICCALL] = 0x02 + + checkTree := func(opcode vm.OpCode, dest common.Address) map[common.Hash]common.Hash { + colors.PrintBlue("Checking storage after call tree with ", opcode) + slots := make(map[common.Hash]common.Hash) + zeroHashBytes := common.BigToHash(common.Big0).Bytes() + + var nest func(level uint) []uint8 + nest = func(level uint) []uint8 { + args := []uint8{} + + if level == 0 { + // call storage.wasm + args = append(args, kinds[opcode]) + if opcode == vm.CALL { + args = append(args, zeroHashBytes...) + } + args = append(args, storeAddr[:]...) + + key := testhelpers.RandomHash() + value := testhelpers.RandomHash() + slots[key] = value + + // insert value @ key + args = append(args, argsForStorageWrite(key, value)...) + return args + } + + // do the two following calls + args = append(args, kinds[opcode]) + if opcode == vm.CALL { + args = append(args, zeroHashBytes...) + } + args = append(args, callsAddr[:]...) + args = append(args, 2) + + for i := 0; i < 2; i++ { + inner := nest(level - 1) + args = append(args, arbmath.Uint32ToBytes(uint32(len(inner)))...) + args = append(args, inner...) + } + return args + } + var tree []uint8 + if opcode == vm.CALL { + tree = nest(3)[53:] + } else { + tree = nest(3)[21:] + } + tx = l2info.PrepareTxTo("Owner", &callsAddr, 1e9, nil, tree) + ensure(tx, l2client.SendTransaction(ctx, tx)) + + for key, value := range slots { + assertStorageAt(t, ctx, l2client, dest, key, value) + } + return slots + } + + slots := checkTree(vm.CALL, storeAddr) + checkTree(vm.DELEGATECALL, callsAddr) + + colors.PrintBlue("Checking static call") + calldata := []byte{0} + expected := []byte{} + for key, value := range slots { + calldata = multicallAppend(calldata, vm.STATICCALL, storeAddr, argsForStorageRead(key)) + expected = append(expected, value[:]...) + } + values := sendContractCall(t, ctx, callsAddr, l2client, calldata) + if !bytes.Equal(expected, values) { + Fatal(t, "wrong results static call", common.Bytes2Hex(expected), common.Bytes2Hex(values)) + } + tx = l2info.PrepareTxTo("Owner", &callsAddr, 1e9, nil, calldata) + ensure(tx, l2client.SendTransaction(ctx, tx)) + + colors.PrintBlue("Checking static call write protection") + writeKey := append([]byte{0x1}, testhelpers.RandomHash().Bytes()...) + writeKey = append(writeKey, testhelpers.RandomHash().Bytes()...) + expectFailure(callsAddr, argsForMulticall(vm.STATICCALL, storeAddr, nil, writeKey), "") + + // mechanisms for creating calldata + burnArbGas, _ := util.NewCallParser(pgen.ArbosTestABI, "burnArbGas") + customRevert, _ := util.NewCallParser(pgen.ArbDebugABI, "customRevert") + legacyError, _ := util.NewCallParser(pgen.ArbDebugABI, "legacyError") + callKeccak, _ := util.NewCallParser(mocksgen.ProgramTestABI, "callKeccak") + pack := func(data []byte, err error) []byte { + Require(t, err) + return data + } + + colors.PrintBlue("Calling the ArbosTest precompile (Rust => precompile)") + testPrecompile := func(gas uint64) uint64 { + // Call the burnArbGas() precompile from Rust + burn := pack(burnArbGas(big.NewInt(int64(gas)))) + args := argsForMulticall(vm.CALL, types.ArbosTestAddress, nil, burn) + tx := l2info.PrepareTxTo("Owner", &callsAddr, 1e9, nil, args) + receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) + return receipt.GasUsed - receipt.GasUsedForL1 + } + + smallGas := testhelpers.RandomUint64(2000, 8000) + largeGas := smallGas + testhelpers.RandomUint64(2000, 8000) + small := testPrecompile(smallGas) + large := testPrecompile(largeGas) + + if !arbmath.Within(large-small, largeGas-smallGas, 2) { + ratio := float64(int64(large)-int64(small)) / float64(int64(largeGas)-int64(smallGas)) + Fatal(t, "inconsistent burns", large, small, largeGas, smallGas, ratio) + } + + colors.PrintBlue("Checking consensus revert data (Rust => precompile)") + args := argsForMulticall(vm.CALL, types.ArbDebugAddress, nil, pack(customRevert(uint64(32)))) + spider := ": error Custom(32, This spider family wards off bugs: /\\oo/\\ //\\(oo)//\\ /\\oo/\\, true)" + expectFailure(callsAddr, args, spider) + + colors.PrintBlue("Checking non-consensus revert data (Rust => precompile)") + args = argsForMulticall(vm.CALL, types.ArbDebugAddress, nil, pack(legacyError())) + expectFailure(callsAddr, args, "") + + colors.PrintBlue("Checking success (Rust => Solidity => Rust)") + rustArgs := append([]byte{0x01}, []byte(spider)...) + mockArgs := argsForMulticall(vm.CALL, mockAddr, nil, pack(callKeccak(keccakAddr, rustArgs))) + tx = l2info.PrepareTxTo("Owner", &callsAddr, 1e9, nil, mockArgs) + ensure(tx, l2client.SendTransaction(ctx, tx)) + + colors.PrintBlue("Checking call with value (Rust => EOA)") + eoa := testhelpers.RandomAddress() + value := testhelpers.RandomCallValue(1e12) + args = argsForMulticall(vm.CALL, eoa, value, []byte{}) + tx = l2info.PrepareTxTo("Owner", &callsAddr, 1e9, value, args) + ensure(tx, l2client.SendTransaction(ctx, tx)) + balance := GetBalance(t, ctx, l2client, eoa) + if !arbmath.BigEquals(balance, value) { + Fatal(t, balance, value) + } + + blocks := []uint64{10} + validateBlockRange(t, blocks, jit, builder) +} + +func TestProgramReturnData(t *testing.T) { + t.Parallel() + testReturnData(t, true) +} + +func testReturnData(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) { + t.Helper() + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + } + + readReturnDataAddr := deployWasm(t, ctx, auth, l2client, rustFile("read-return-data")) + + colors.PrintGrey("read-return-data.evm ", readReturnDataAddr) + colors.PrintBlue("checking calls with partial return data") + + dataToSend := [4]byte{0, 1, 2, 3} + testReadReturnData := func(callType uint32, offset uint32, size uint32, expectedSize uint32, count uint32) { + parameters := [20]byte{} + binary.BigEndian.PutUint32(parameters[0:4], callType) + binary.BigEndian.PutUint32(parameters[4:8], offset) + binary.BigEndian.PutUint32(parameters[8:12], size) + binary.BigEndian.PutUint32(parameters[12:16], expectedSize) + binary.BigEndian.PutUint32(parameters[16:20], count) + callData := append(parameters[:], dataToSend[:]...) + + tx := l2info.PrepareTxTo("Owner", &readReturnDataAddr, 1e9, nil, callData) + ensure(tx, l2client.SendTransaction(ctx, tx)) + } + + testReadReturnData(1, 0, 5, 4, 2) + testReadReturnData(1, 0, 1, 1, 2) + testReadReturnData(1, 5, 1, 0, 2) + testReadReturnData(1, 0, 0, 0, 2) + testReadReturnData(1, 0, 4, 4, 2) + + testReadReturnData(2, 0, 5, 4, 1) + testReadReturnData(2, 0, 1, 1, 1) + testReadReturnData(2, 5, 1, 0, 1) + testReadReturnData(2, 0, 0, 0, 1) + testReadReturnData(2, 0, 4, 4, 1) + + validateBlocks(t, 11, jit, builder) +} + +func TestProgramLogs(t *testing.T) { + t.Parallel() + testLogs(t, true) +} + +func testLogs(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + logAddr := deployWasm(t, ctx, auth, l2client, rustFile("log")) + multiAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + encode := func(topics []common.Hash, data []byte) []byte { + args := []byte{byte(len(topics))} + for _, topic := range topics { + args = append(args, topic[:]...) + } + args = append(args, data...) + return args + } + randBytes := func(min, max uint64) []byte { + return testhelpers.RandomSlice(testhelpers.RandomUint64(min, max)) + } + + for i := 0; i <= 4; i++ { + colors.PrintGrey("Emitting ", i, " topics") + topics := make([]common.Hash, i) + for j := 0; j < i; j++ { + topics[j] = testhelpers.RandomHash() + } + data := randBytes(0, 48) + args := encode(topics, data) + tx := l2info.PrepareTxTo("Owner", &logAddr, 1e9, nil, args) + receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) + + if len(receipt.Logs) != 1 { + Fatal(t, "wrong number of logs", len(receipt.Logs)) + } + log := receipt.Logs[0] + if !bytes.Equal(log.Data, data) { + Fatal(t, "data mismatch", log.Data, data) + } + if len(log.Topics) != len(topics) { + Fatal(t, "topics mismatch", len(log.Topics), len(topics)) + } + for j := 0; j < i; j++ { + if log.Topics[j] != topics[j] { + Fatal(t, "topic mismatch", log.Topics, topics) + } + } + } + + tooMany := encode([]common.Hash{{}, {}, {}, {}, {}}, []byte{}) + tx := l2info.PrepareTxTo("Owner", &logAddr, 1e9, nil, tooMany) + Require(t, l2client.SendTransaction(ctx, tx)) + EnsureTxFailed(t, ctx, l2client, tx) + + delegate := argsForMulticall(vm.DELEGATECALL, logAddr, nil, []byte{0x00}) + tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, nil, delegate) + receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) + if receipt.Logs[0].Address != multiAddr { + Fatal(t, "wrong address", receipt.Logs[0].Address) + } + + validateBlocks(t, 11, jit, builder) +} + +func TestProgramCreate(t *testing.T) { + t.Parallel() + testCreate(t, true) +} + +func testCreate(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + createAddr := deployWasm(t, ctx, auth, l2client, rustFile("create")) + activateAuth := auth + activateAuth.Value = oneEth + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + deployWasm, _ := readWasmFile(t, rustFile("storage")) + deployCode := deployContractInitCode(deployWasm, false) + startValue := testhelpers.RandomCallValue(1e12) + salt := testhelpers.RandomHash() + + create := func(createArgs []byte, correctStoreAddr common.Address) { + tx := l2info.PrepareTxTo("Owner", &createAddr, 1e9, startValue, createArgs) + receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) + storeAddr := common.BytesToAddress(receipt.Logs[0].Topics[0][:]) + if storeAddr == (common.Address{}) { + Fatal(t, "failed to deploy storage.wasm") + } + colors.PrintBlue("deployed keccak to ", storeAddr.Hex()) + balance, err := l2client.BalanceAt(ctx, storeAddr, nil) + Require(t, err) + if !arbmath.BigEquals(balance, startValue) { + Fatal(t, "storage.wasm has the wrong balance", balance, startValue) + } + + // activate the program + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + tx, err = arbWasm.ActivateProgram(&activateAuth, storeAddr) + if err != nil { + if !strings.Contains(err.Error(), "ProgramUpToDate") { + Fatal(t, err) + } + } else { + _, succeedErr := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, succeedErr) + } + + // check the program works + key := testhelpers.RandomHash() + value := testhelpers.RandomHash() + tx = l2info.PrepareTxTo("Owner", &storeAddr, 1e9, nil, argsForStorageWrite(key, value)) + ensure(tx, l2client.SendTransaction(ctx, tx)) + assertStorageAt(t, ctx, l2client, storeAddr, key, value) + + if storeAddr != correctStoreAddr { + Fatal(t, "program deployed to the wrong address", storeAddr, correctStoreAddr) + } + } + + create1Args := []byte{0x01} + create1Args = append(create1Args, common.BigToHash(startValue).Bytes()...) + create1Args = append(create1Args, deployCode...) + + create2Args := []byte{0x02} + create2Args = append(create2Args, common.BigToHash(startValue).Bytes()...) + create2Args = append(create2Args, salt[:]...) + create2Args = append(create2Args, deployCode...) + + create1Addr := crypto.CreateAddress(createAddr, 1) + create2Addr := crypto.CreateAddress2(createAddr, salt, crypto.Keccak256(deployCode)) + create(create1Args, create1Addr) + create(create2Args, create2Addr) + + revertData := []byte("✌(✰‿✰)✌ ┏(✰‿✰)┛ ┗(✰‿✰)┓ ┗(✰‿✰)┛ ┏(✰‿✰)┓ ✌(✰‿✰)✌") + revertArgs := []byte{0x01} + revertArgs = append(revertArgs, common.BigToHash(startValue).Bytes()...) + revertArgs = append(revertArgs, deployContractInitCode(revertData, true)...) + + _, tx, mock, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + auth.Value = startValue + ensure(mock.CheckRevertData(&auth, createAddr, revertArgs, revertData)) + + // validate just the opcodes + blocks := []uint64{5, 6} + validateBlockRange(t, blocks, jit, builder) +} + +func TestProgramMemory(t *testing.T) { + t.Parallel() + testMemory(t, true) +} + +func testMemory(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, l2client) + Require(t, err) + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + + ensure(arbOwner.SetInkPrice(&auth, 1e4)) + ensure(arbOwner.SetMaxTxGasLimit(&auth, 34000000)) + + memoryAddr := deployWasm(t, ctx, auth, l2client, watFile("memory")) + multiAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + growCallAddr := deployWasm(t, ctx, auth, l2client, watFile("grow/grow-and-call")) + growFixed := deployWasm(t, ctx, auth, l2client, watFile("grow/fixed")) + memWrite := deployWasm(t, ctx, auth, l2client, watFile("grow/mem-write")) + + expectFailure := func(to common.Address, data []byte, value *big.Int) { + t.Helper() + msg := ethereum.CallMsg{ + To: &to, + Value: big.NewInt(0), + Data: data, + Gas: 32000000, + } + _, err := l2client.CallContract(ctx, msg, nil) + if err == nil { + Fatal(t, "call should have failed") + } + + // execute onchain for proving's sake + tx := l2info.PrepareTxTo("Owner", &to, 1e9, value, data) + Require(t, l2client.SendTransaction(ctx, tx)) + EnsureTxFailed(t, ctx, l2client, tx) + } + + model := programs.NewMemoryModel(programs.InitialFreePages, programs.InitialPageGas) + + // expand to 128 pages, retract, then expand again to 128. + // - multicall takes 1 page to init, and then 1 more at runtime. + // - grow-and-call takes 1 page, then grows to the first arg by second arg steps. + args := argsForMulticall(vm.CALL, memoryAddr, nil, []byte{126, 50}) + args = multicallAppend(args, vm.CALL, memoryAddr, []byte{126, 80}) + + tx := l2info.PrepareTxTo("Owner", &multiAddr, 1e9, nil, args) + receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) + gasCost := receipt.GasUsedForL2() + memCost := model.GasCost(128, 0, 0) + model.GasCost(126, 2, 128) + logical := uint64(32000000 + 126*programs.InitialPageGas) + if !arbmath.WithinRange(gasCost, memCost, memCost+2e5) || !arbmath.WithinRange(gasCost, logical, logical+2e5) { + Fatal(t, "unexpected cost", gasCost, memCost, logical) + } + + // check that we'd normally run out of gas + ensure(arbOwner.SetMaxTxGasLimit(&auth, 32000000)) + expectFailure(multiAddr, args, oneEth) + + // check that activation fails when out of memory + wasm, _ := readWasmFile(t, watFile("grow/grow-120")) + growHugeAddr := deployContract(t, ctx, auth, l2client, wasm) + colors.PrintGrey("memory.wat ", memoryAddr) + colors.PrintGrey("multicall.rs ", multiAddr) + colors.PrintGrey("grow-and-call.wat ", growCallAddr) + colors.PrintGrey("grow-120.wat ", growHugeAddr) + activate, _ := util.NewCallParser(pgen.ArbWasmABI, "activateProgram") + pack := func(data []byte, err error) []byte { + Require(t, err) + return data + } + args = arbmath.ConcatByteSlices([]byte{60}, types.ArbWasmAddress[:], pack(activate(growHugeAddr))) + expectFailure(growCallAddr, args, oneEth) // consumes 64, then tries to compile something 120 + + // check that activation then succeeds + args[0] = 0x00 + tx = l2info.PrepareTxTo("Owner", &growCallAddr, 1e9, oneEth, args) + receipt = ensure(tx, l2client.SendTransaction(ctx, tx)) + if receipt.GasUsedForL2() < 1659168 { + Fatal(t, "activation unexpectedly cheap") + } + + // check footprint can induce a revert + args = arbmath.ConcatByteSlices([]byte{122}, growCallAddr[:], []byte{0}, common.Address{}.Bytes()) + expectFailure(growCallAddr, args, oneEth) + + // check same call would have succeeded with fewer pages + args = arbmath.ConcatByteSlices([]byte{119}, growCallAddr[:], []byte{0}, common.Address{}.Bytes()) + tx = l2info.PrepareTxTo("Owner", &growCallAddr, 1e9, nil, args) + receipt = ensure(tx, l2client.SendTransaction(ctx, tx)) + gasCost = receipt.GasUsedForL2() + memCost = model.GasCost(127, 0, 0) + if !arbmath.WithinRange(gasCost, memCost, memCost+1e5) { + Fatal(t, "unexpected cost", gasCost, memCost) + } + + // check huge memory footprint + programMemoryFootprint, err := arbWasm.ProgramMemoryFootprint(nil, growHugeAddr) + Require(t, err) + if programMemoryFootprint != 120 { + Fatal(t, "unexpected memory footprint", programMemoryFootprint) + } + + // check edge case where memory doesn't require `pay_for_memory_grow` + tx = l2info.PrepareTxTo("Owner", &growFixed, 1e9, nil, args) + ensure(tx, l2client.SendTransaction(ctx, tx)) + + // check memory boundary conditions + type Case struct { + pass bool + size uint8 + spot uint32 + data uint32 + } + cases := []Case{ + {true, 0, 0, 0}, + {true, 1, 4, 0}, + {true, 1, 65536, 0}, + {false, 1, 65536, 1}, // 1st byte out of bounds + {false, 1, 65537, 0}, // 2nd byte out of bounds + {true, 1, 65535, 1}, // last byte in bounds + {false, 1, 65535, 2}, // 1st byte over-run + {true, 2, 131072, 0}, + {false, 2, 131073, 0}, + } + for _, test := range cases { + args := []byte{} + if test.size > 0 { + args = append(args, test.size) + args = binary.LittleEndian.AppendUint32(args, test.spot) + args = binary.LittleEndian.AppendUint32(args, test.data) + } + if test.pass { + tx = l2info.PrepareTxTo("Owner", &memWrite, 1e9, nil, args) + ensure(tx, l2client.SendTransaction(ctx, tx)) + } else { + expectFailure(memWrite, args, nil) + } + } + + validateBlocks(t, 3, jit, builder) +} + +func TestProgramActivateFails(t *testing.T) { + t.Parallel() + testActivateFails(t, true) +} + +func testActivateFails(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + + badExportWasm, _ := readWasmFile(t, watFile("bad-mods/bad-export")) + auth.GasLimit = 32000000 // skip gas estimation + badExportAddr := deployContract(t, ctx, auth, l2client, badExportWasm) + + blockToValidate := uint64(0) + timed(t, "activate bad-export", func() { + auth.Value = oneEth + tx, err := arbWasm.ActivateProgram(&auth, badExportAddr) + Require(t, err) + txRes, err := WaitForTx(ctx, l2client, tx.Hash(), time.Second*5) + Require(t, err) + if txRes.Status != 0 { + Fatal(t, "bad-export transaction did not fail") + } + gotError := arbutil.DetailTxError(ctx, l2client, tx, txRes) + if !strings.Contains(gotError.Error(), "reserved symbol") { + Fatal(t, "unexpected error: ", gotError) + } + Require(t, err) + blockToValidate = txRes.BlockNumber.Uint64() + }) + + validateBlockRange(t, []uint64{blockToValidate}, jit, builder) +} + +func TestProgramSdkStorage(t *testing.T) { + t.Parallel() + testSdkStorage(t, true) +} + +func testSdkStorage(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + rust := deployWasm(t, ctx, auth, l2client, rustFile("sdk-storage")) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + solidity, tx, mock, err := mocksgen.DeploySdkStorage(&auth, l2client) + ensure(tx, err) + tx, err = mock.Populate(&auth) + receipt := ensure(tx, err) + solCost := receipt.GasUsedForL2() + + tx = l2info.PrepareTxTo("Owner", &rust, 1e9, nil, tx.Data()) + receipt = ensure(tx, l2client.SendTransaction(ctx, tx)) + rustCost := receipt.GasUsedForL2() + + check := func() { + colors.PrintBlue("rust ", rustCost, " sol ", solCost) + + // ensure txes are sequenced before checking state + waitForSequencer(t, builder, receipt.BlockNumber.Uint64()) + + bc := builder.L2.ExecNode.Backend.ArbInterface().BlockChain() + statedb, err := bc.State() + Require(t, err) + + solTrie := statedb.GetStorageRoot(solidity) + rustTrie := statedb.GetStorageRoot(rust) + if solTrie != rustTrie { + Fatal(t, solTrie, rustTrie) + } + } + + check() + + colors.PrintBlue("checking removal") + tx, err = mock.Remove(&auth) + receipt = ensure(tx, err) + solCost = receipt.GasUsedForL2() + + tx = l2info.PrepareTxTo("Owner", &rust, 1e9, nil, tx.Data()) + receipt = ensure(tx, l2client.SendTransaction(ctx, tx)) + rustCost = receipt.GasUsedForL2() + check() +} + +func TestProgramActivationLogs(t *testing.T) { + t.Parallel() + builder, auth, cleanup := setupProgramTest(t, true) + l2client := builder.L2.Client + ctx := builder.ctx + defer cleanup() + + wasm, _ := readWasmFile(t, watFile("memory")) + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + + nolimitAuth := auth + nolimitAuth.GasLimit = 32000000 + + programAddress := deployContract(t, ctx, nolimitAuth, l2client, wasm) + + auth.Value = oneEth + tx, err := arbWasm.ActivateProgram(&auth, programAddress) + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + + if len(receipt.Logs) != 1 { + Fatal(t, "expected 1 log while activating, got ", len(receipt.Logs)) + } + log, err := arbWasm.ParseProgramActivated(*receipt.Logs[0]) + if err != nil { + Fatal(t, "parsing activated log: ", err) + } + if log.Version == 0 { + Fatal(t, "activated program with version 0") + } + if log.Program != programAddress { + Fatal(t, "unexpected program in activation log: ", log.Program) + } + if crypto.Keccak256Hash(wasm) != log.Codehash { + Fatal(t, "unexpected codehash in activation log: ", log.Codehash) + } +} + +func TestProgramEarlyExit(t *testing.T) { + t.Parallel() + testEarlyExit(t, true) +} + +func testEarlyExit(t *testing.T, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + earlyAddress := deployWasm(t, ctx, auth, l2client, "../arbitrator/stylus/tests/exit-early/exit-early.wat") + panicAddress := deployWasm(t, ctx, auth, l2client, "../arbitrator/stylus/tests/exit-early/panic-after-write.wat") + + ensure := func(tx *types.Transaction, err error) { + t.Helper() + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + } + + _, tx, mock, err := mocksgen.DeployProgramTest(&auth, l2client) + ensure(tx, err) + + // revert with the following data + data := append([]byte{0x01}, []byte("private key: https://www.youtube.com/watch?v=dQw4w9WgXcQ")...) + + ensure(mock.CheckRevertData(&auth, earlyAddress, data, data)) + ensure(mock.CheckRevertData(&auth, panicAddress, data, []byte{})) + + validateBlocks(t, 8, jit, builder) +} + +func TestProgramCacheManager(t *testing.T) { + builder, ownerAuth, cleanup := setupProgramTest(t, true) + ctx := builder.ctx + l2client := builder.L2.Client + l2info := builder.L2Info + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + denytx := func(tx *types.Transaction, err error) { + t.Helper() + Require(t, err) + signer := types.LatestSignerForChainID(tx.ChainId()) + from, err := signer.Sender(tx) + Require(t, err) + msg := ethereum.CallMsg{ + To: tx.To(), + Value: big.NewInt(0), + Data: tx.Data(), + From: from, + } + _, err = l2client.CallContract(ctx, msg, nil) + if err == nil { + Fatal(t, "call should have failed") + } + } + assert := func(cond bool, err error, msg ...interface{}) { + t.Helper() + Require(t, err) + if !cond { + Fatal(t, msg...) + } + } + + // precompiles we plan to use + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, builder.L2.Client) + Require(t, err) + arbWasmCache, err := pgen.NewArbWasmCache(types.ArbWasmCacheAddress, builder.L2.Client) + Require(t, err) + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&ownerAuth, 10_000)) + parseLog := logParser[pgen.ArbWasmCacheUpdateProgramCache](t, pgen.ArbWasmCacheABI, "UpdateProgramCache") + + // fund a user account we'll use to probe access-restricted methods + l2info.GenerateAccount("Anyone") + userAuth := l2info.GetDefaultTransactOpts("Anyone", ctx) + userAuth.GasLimit = 3e6 + TransferBalance(t, "Owner", "Anyone", arbmath.BigMulByUint(oneEth, 32), l2info, l2client, ctx) + + // deploy without activating a wasm + wasm, _ := readWasmFile(t, rustFile("keccak")) + program := deployContract(t, ctx, userAuth, l2client, wasm) + codehash := crypto.Keccak256Hash(wasm) + + // try to manage the cache without authorization + manager, tx, mock, err := mocksgen.DeploySimpleCacheManager(&ownerAuth, l2client) + ensure(tx, err) + denytx(mock.CacheProgram(&userAuth, program)) + denytx(mock.EvictProgram(&userAuth, program)) + + // check non-membership + isManager, err := arbWasmCache.IsCacheManager(nil, manager) + assert(!isManager, err) + + // athorize the manager + ensure(arbOwner.AddWasmCacheManager(&ownerAuth, manager)) + assert(arbWasmCache.IsCacheManager(nil, manager)) + all, err := arbWasmCache.AllCacheManagers(nil) + assert(len(all) == 1 && all[0] == manager, err) + + // try to cache something inactive + denytx(mock.CacheProgram(&userAuth, program)) + ensure(mock.EvictProgram(&userAuth, program)) + denytx(mock.CacheProgram(&userAuth, testhelpers.RandomAddress())) + ensure(mock.EvictProgram(&userAuth, testhelpers.RandomAddress())) + + // cache the active program + activateWasm(t, ctx, userAuth, l2client, program, "keccak") + ensure(mock.CacheProgram(&userAuth, program)) + assert(arbWasmCache.CodehashIsCached(nil, codehash)) + + // compare gas costs + keccak := func() uint64 { + tx := l2info.PrepareTxTo("Owner", &program, 1e9, nil, []byte{0x00}) + return ensure(tx, l2client.SendTransaction(ctx, tx)).GasUsedForL2() + } + ensure(mock.EvictProgram(&userAuth, program)) + miss := keccak() + ensure(mock.CacheProgram(&userAuth, program)) + hits := keccak() + cost, err := arbWasm.ProgramInitGas(nil, program) + assert(hits-cost.GasWhenCached == miss-cost.Gas, err) + + // check logs + empty := len(ensure(mock.CacheProgram(&userAuth, program)).Logs) + evict := parseLog(ensure(mock.EvictProgram(&userAuth, program)).Logs[0]) + cache := parseLog(ensure(mock.CacheProgram(&userAuth, program)).Logs[0]) + assert(empty == 0 && evict.Manager == manager && !evict.Cached && cache.Codehash == codehash && cache.Cached, nil) + + // check ownership + assert(arbOwner.IsChainOwner(nil, ownerAuth.From)) + ensure(arbWasmCache.EvictCodehash(&ownerAuth, codehash)) + ensure(arbWasmCache.CacheCodehash(&ownerAuth, codehash)) + + // de-authorize manager + ensure(arbOwner.RemoveWasmCacheManager(&ownerAuth, manager)) + denytx(mock.EvictProgram(&userAuth, program)) + assert(arbWasmCache.CodehashIsCached(nil, codehash)) + all, err = arbWasmCache.AllCacheManagers(nil) + assert(len(all) == 0, err) +} + +func setupProgramTest(t *testing.T, jit bool) ( + *NodeBuilder, bind.TransactOpts, func(), +) { + ctx, cancel := context.WithCancel(context.Background()) + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + + builder.nodeConfig.BlockValidator.Enable = false + builder.nodeConfig.Staker.Enable = true + builder.nodeConfig.BatchPoster.Enable = true + builder.nodeConfig.ParentChainReader.Enable = true + builder.nodeConfig.ParentChainReader.OldHeaderTimeout = 10 * time.Minute + + valConf := valnode.TestValidationConfig + valConf.UseJit = jit + _, valStack := createTestValidationNode(t, ctx, &valConf) + configByValidationNode(builder.nodeConfig, valStack) + + builder.execConfig.Sequencer.MaxRevertGasReject = 0 + + builderCleanup := builder.Build(t) + + cleanup := func() { + builderCleanup() + cancel() + } + + auth := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + arbDebug, err := pgen.NewArbDebug(types.ArbDebugAddress, builder.L2.Client) + Require(t, err) + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, builder.L2.Client, tx) + Require(t, err) + return receipt + } + + // Set random pricing params + inkPrice := testhelpers.RandomUint32(1, 20000) // evm to ink + colors.PrintGrey(fmt.Sprintf("ink price=%d", inkPrice)) + + ensure(arbDebug.BecomeChainOwner(&auth)) + ensure(arbOwner.SetInkPrice(&auth, inkPrice)) + return builder, auth, cleanup +} + +func readWasmFile(t *testing.T, file string) ([]byte, []byte) { + t.Helper() + name := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) + source, err := os.ReadFile(file) + Require(t, err) + + // chose a random dictionary for testing, but keep the same files consistent + randDict := arbcompress.Dictionary((len(file) + len(t.Name())) % 2) + + wasmSource, err := wasmer.Wat2Wasm(string(source)) + Require(t, err) + wasm, err := arbcompress.Compress(wasmSource, arbcompress.LEVEL_WELL, randDict) + Require(t, err) + + toKb := func(data []byte) float64 { return float64(len(data)) / 1024.0 } + colors.PrintGrey(fmt.Sprintf("%v: len %.2fK vs %.2fK", name, toKb(wasm), toKb(wasmSource))) + + wasm = append(state.NewStylusPrefix(byte(randDict)), wasm...) + return wasm, wasmSource +} + +func deployWasm( + t *testing.T, ctx context.Context, auth bind.TransactOpts, l2client *ethclient.Client, file string, +) common.Address { + name := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) + wasm, _ := readWasmFile(t, file) + auth.GasLimit = 32000000 // skip gas estimation + program := deployContract(t, ctx, auth, l2client, wasm) + colors.PrintGrey(name, ": deployed to ", program.Hex()) + activateWasm(t, ctx, auth, l2client, program, name) + return program +} + +func activateWasm( + t *testing.T, + ctx context.Context, + auth bind.TransactOpts, + l2client *ethclient.Client, + program common.Address, + name string, +) { + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + + timed(t, "activate "+name, func() { + auth.Value = oneEth + tx, err := arbWasm.ActivateProgram(&auth, program) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + }) +} + +func argsForStorageRead(key common.Hash) []byte { + args := []byte{0x00} + args = append(args, key[:]...) + return args +} + +func argsForStorageWrite(key, value common.Hash) []byte { + args := []byte{0x01} + args = append(args, key[:]...) + args = append(args, value[:]...) + return args +} + +func argsForMulticall(opcode vm.OpCode, address common.Address, value *big.Int, calldata []byte) []byte { + kinds := make(map[vm.OpCode]byte) + kinds[vm.CALL] = 0x00 + kinds[vm.DELEGATECALL] = 0x01 + kinds[vm.STATICCALL] = 0x02 + + args := []byte{0x01} + length := 21 + len(calldata) + if opcode == vm.CALL { + length += 32 + } + args = append(args, arbmath.Uint32ToBytes(uint32(length))...) + args = append(args, kinds[opcode]) + if opcode == vm.CALL { + if value == nil { + value = common.Big0 + } + args = append(args, common.BigToHash(value).Bytes()...) + } + args = append(args, address.Bytes()...) + args = append(args, calldata...) + return args +} + +func multicallAppend(calls []byte, opcode vm.OpCode, address common.Address, inner []byte) []byte { + calls[0] += 1 // add another call + calls = append(calls, argsForMulticall(opcode, address, nil, inner)[1:]...) + return calls +} + +func assertStorageAt( + t *testing.T, ctx context.Context, l2client *ethclient.Client, contract common.Address, key, value common.Hash, +) { + t.Helper() + storedBytes, err := l2client.StorageAt(ctx, contract, key, nil) + Require(t, err) + storedValue := common.BytesToHash(storedBytes) + if value != storedValue { + Fatal(t, "wrong value", value, storedValue) + } +} + +func rustFile(name string) string { + return fmt.Sprintf("../arbitrator/stylus/tests/%v/target/wasm32-unknown-unknown/release/%v.wasm", name, name) +} + +func watFile(name string) string { + return fmt.Sprintf("../arbitrator/stylus/tests/%v.wat", name) +} + +func waitForSequencer(t *testing.T, builder *NodeBuilder, block uint64) { + t.Helper() + msgCount := arbutil.BlockNumberToMessageCount(block, 0) + doUntil(t, 20*time.Millisecond, 500, func() bool { + batchCount, err := builder.L2.ConsensusNode.InboxTracker.GetBatchCount() + Require(t, err) + meta, err := builder.L2.ConsensusNode.InboxTracker.GetBatchMetadata(batchCount - 1) + Require(t, err) + msgExecuted, err := builder.L2.ExecNode.ExecEngine.HeadMessageNumber() + Require(t, err) + return msgExecuted+1 >= msgCount && meta.MessageCount >= msgCount + }) +} + +func timed(t *testing.T, message string, lambda func()) { + t.Helper() + now := time.Now() + lambda() + passed := time.Since(now) + colors.PrintGrey("Time to ", message, ": ", passed.String()) +} + +func formatTime(duration time.Duration) string { + span := float64(duration.Nanoseconds()) + unit := 0 + units := []string{"ns", "μs", "ms", "s", "min", "h", "d", "w", "mo", "yr", "dec", "cent", "mill", "eon"} + scale := []float64{1000., 1000., 1000., 60., 60., 24., 7., 4.34, 12., 10., 10., 10., 1000000.} + for span >= scale[unit] && unit < len(scale) { + span /= scale[unit] + unit += 1 + } + return fmt.Sprintf("%.2f%s", span, units[unit]) +} + +func TestWasmRecreate(t *testing.T) { + builder, auth, cleanup := setupProgramTest(t, true) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + storage := deployWasm(t, ctx, auth, l2client, rustFile("storage")) + + zero := common.Hash{} + val := common.HexToHash("0x121233445566") + + // do an onchain call - store value + storeTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageWrite(zero, val)) + Require(t, l2client.SendTransaction(ctx, storeTx)) + _, err := EnsureTxSucceeded(ctx, l2client, storeTx) + Require(t, err) + + testDir := t.TempDir() + nodeBStack := createStackConfigForTest(testDir) + nodeB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + + _, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx) + Require(t, err) + + // make sure reading 2nd value succeeds from 2nd node + loadTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageRead(zero)) + result, err := arbutil.SendTxAsCall(ctx, nodeB.Client, loadTx, l2info.GetAddress("Owner"), nil, true) + Require(t, err) + if common.BytesToHash(result) != val { + Fatal(t, "got wrong value") + } + // close nodeB + cleanupB() + + // delete wasm dir of nodeB + + wasmPath := filepath.Join(testDir, "system_tests.test", "wasm") + dirContents, err := os.ReadDir(wasmPath) + Require(t, err) + if len(dirContents) == 0 { + Fatal(t, "not contents found before delete") + } + os.RemoveAll(wasmPath) + + // recreate nodeB - using same source dir (wasm deleted) + nodeB, cleanupB = builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + + // test nodeB - sees existing transaction + _, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx) + Require(t, err) + + // test nodeB - answers eth_call (requires reloading wasm) + result, err = arbutil.SendTxAsCall(ctx, nodeB.Client, loadTx, l2info.GetAddress("Owner"), nil, true) + Require(t, err) + if common.BytesToHash(result) != val { + Fatal(t, "got wrong value") + } + + // send new tx (requires wasm) and check nodeB sees it as well + Require(t, l2client.SendTransaction(ctx, loadTx)) + + _, err = EnsureTxSucceeded(ctx, l2client, loadTx) + Require(t, err) + + _, err = EnsureTxSucceeded(ctx, nodeB.Client, loadTx) + Require(t, err) + + cleanupB() + dirContents, err = os.ReadDir(wasmPath) + Require(t, err) + if len(dirContents) == 0 { + Fatal(t, "not contents found before delete") + } + os.RemoveAll(wasmPath) + +} + +// createMapFromDb is used in verifying if wasm store rebuilding works +func createMapFromDb(db ethdb.KeyValueStore) (map[string][]byte, error) { + iter := db.NewIterator(nil, nil) + defer iter.Release() + + dataMap := make(map[string][]byte) + + for iter.Next() { + key := iter.Key() + value := iter.Value() + + dataMap[string(key)] = value + } + + if err := iter.Error(); err != nil { + return nil, fmt.Errorf("iterator error: %w", err) + } + + return dataMap, nil +} + +func TestWasmStoreRebuilding(t *testing.T) { + builder, auth, cleanup := setupProgramTest(t, true) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + storage := deployWasm(t, ctx, auth, l2client, rustFile("storage")) + + zero := common.Hash{} + val := common.HexToHash("0x121233445566") + + // do an onchain call - store value + storeTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageWrite(zero, val)) + Require(t, l2client.SendTransaction(ctx, storeTx)) + _, err := EnsureTxSucceeded(ctx, l2client, storeTx) + Require(t, err) + + testDir := t.TempDir() + nodeBStack := createStackConfigForTest(testDir) + nodeB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + + _, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx) + Require(t, err) + + // make sure reading 2nd value succeeds from 2nd node + loadTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageRead(zero)) + result, err := arbutil.SendTxAsCall(ctx, nodeB.Client, loadTx, l2info.GetAddress("Owner"), nil, true) + Require(t, err) + if common.BytesToHash(result) != val { + Fatal(t, "got wrong value") + } + + wasmDb := nodeB.ExecNode.Backend.ArbInterface().BlockChain().StateCache().WasmStore() + + storeMap, err := createMapFromDb(wasmDb) + Require(t, err) + + // close nodeB + cleanupB() + + // delete wasm dir of nodeB + wasmPath := filepath.Join(testDir, "system_tests.test", "wasm") + dirContents, err := os.ReadDir(wasmPath) + Require(t, err) + if len(dirContents) == 0 { + Fatal(t, "not contents found before delete") + } + os.RemoveAll(wasmPath) + + // recreate nodeB - using same source dir (wasm deleted) + nodeB, cleanupB = builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + bc := nodeB.ExecNode.Backend.ArbInterface().BlockChain() + + wasmDbAfterDelete := nodeB.ExecNode.Backend.ArbInterface().BlockChain().StateCache().WasmStore() + storeMapAfterDelete, err := createMapFromDb(wasmDbAfterDelete) + Require(t, err) + if len(storeMapAfterDelete) != 0 { + Fatal(t, "non-empty wasm store after it was previously deleted") + } + + // Start rebuilding and wait for it to finish + log.Info("starting rebuilding of wasm store") + Require(t, gethexec.RebuildWasmStore(ctx, wasmDbAfterDelete, nodeB.ExecNode.ChainDB, nodeB.ExecNode.ConfigFetcher().RPC.MaxRecreateStateDepth, bc, common.Hash{}, bc.CurrentBlock().Hash())) + + wasmDbAfterRebuild := nodeB.ExecNode.Backend.ArbInterface().BlockChain().StateCache().WasmStore() + + // Before comparing, check if rebuilding was set to done and then delete the keys that are used to track rebuilding status + status, err := gethexec.ReadFromKeyValueStore[common.Hash](wasmDbAfterRebuild, gethexec.RebuildingPositionKey) + Require(t, err) + if status != gethexec.RebuildingDone { + Fatal(t, "rebuilding was not set to done after successful completion") + } + Require(t, wasmDbAfterRebuild.Delete(gethexec.RebuildingPositionKey)) + Require(t, wasmDbAfterRebuild.Delete(gethexec.RebuildingStartBlockHashKey)) + + rebuiltStoreMap, err := createMapFromDb(wasmDbAfterRebuild) + Require(t, err) + + // Check if rebuilding worked + if len(storeMap) != len(rebuiltStoreMap) { + Fatal(t, "size mismatch while rebuilding wasm store:", "want", len(storeMap), "got", len(rebuiltStoreMap)) + } + for key, value1 := range storeMap { + value2, exists := rebuiltStoreMap[key] + if !exists { + Fatal(t, "rebuilt wasm store doesn't have key from original") + } + if !bytes.Equal(value1, value2) { + Fatal(t, "rebuilt wasm store has incorrect value from original") + } + } + + cleanupB() +} diff --git a/system_tests/pruning_test.go b/system_tests/pruning_test.go index e9e99dffc..041781ac4 100644 --- a/system_tests/pruning_test.go +++ b/system_tests/pruning_test.go @@ -65,7 +65,7 @@ func TestPruning(t *testing.T) { stack, err := node.New(builder.l2StackConfig) Require(t, err) defer stack.Close() - chainDb, err := stack.OpenDatabase("chaindb", 0, 0, "", false) + chainDb, err := stack.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) Require(t, err) defer chainDb.Close() chainDbEntriesBeforePruning := countStateEntries(chainDb) @@ -89,7 +89,8 @@ func TestPruning(t *testing.T) { initConfig := conf.InitConfigDefault initConfig.Prune = "full" coreCacheConfig := gethexec.DefaultCacheConfigFor(stack, &builder.execConfig.Caching) - err = pruning.PruneChainDb(ctx, chainDb, stack, &initConfig, coreCacheConfig, builder.L1.Client, *builder.L2.ConsensusNode.DeployInfo, false) + persistentConfig := conf.PersistentConfigDefault + err = pruning.PruneChainDb(ctx, chainDb, stack, &initConfig, coreCacheConfig, &persistentConfig, builder.L1.Client, *builder.L2.ConsensusNode.DeployInfo, false) Require(t, err) for _, key := range testKeys { diff --git a/system_tests/recreatestate_rpc_test.go b/system_tests/recreatestate_rpc_test.go index 777ed1796..bf321808d 100644 --- a/system_tests/recreatestate_rpc_test.go +++ b/system_tests/recreatestate_rpc_test.go @@ -449,7 +449,7 @@ func testSkippingSavingStateAndRecreatingAfterRestart(t *testing.T, cacheConfig } func TestSkippingSavingStateAndRecreatingAfterRestart(t *testing.T) { - cacheConfig := gethexec.DefaultCachingConfig + cacheConfig := gethexec.TestCachingConfig cacheConfig.Archive = true cacheConfig.SnapshotCache = 0 // disable snapshots cacheConfig.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are diff --git a/system_tests/retryable_test.go b/system_tests/retryable_test.go index b0691db17..1abf9f216 100644 --- a/system_tests/retryable_test.go +++ b/system_tests/retryable_test.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/gasestimator" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" @@ -31,7 +32,7 @@ import ( "github.com/offchainlabs/nitro/util/colors" ) -func retryableSetup(t *testing.T) ( +func retryableSetup(t *testing.T, modifyNodeConfig ...func(*NodeBuilder)) ( *NodeBuilder, *bridgegen.Inbox, func(*types.Receipt) *types.Transaction, @@ -40,6 +41,9 @@ func retryableSetup(t *testing.T) ( ) { ctx, cancel := context.WithCancel(context.Background()) builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + for _, f := range modifyNodeConfig { + f(builder) + } builder.Build(t) builder.L2Info.GenerateAccount("User2") @@ -158,8 +162,12 @@ func TestSubmitRetryableImmediateSuccess(t *testing.T) { Require(t, err, "failed to estimate retryable submission") estimate := tx.Gas() expectedEstimate := params.TxGas + params.TxDataNonZeroGasEIP2028*4 - if estimate != expectedEstimate { - t.Errorf("estimated retryable ticket at %v gas but expected %v", estimate, expectedEstimate) + if float64(estimate) > float64(expectedEstimate)*(1+gasestimator.EstimateGasErrorRatio) { + t.Errorf("estimated retryable ticket at %v gas but expected %v, with error margin of %v", + estimate, + expectedEstimate, + gasestimator.EstimateGasErrorRatio, + ) } // submit & auto redeem the retryable using the gas estimate @@ -200,9 +208,11 @@ func TestSubmitRetryableImmediateSuccess(t *testing.T) { } } -func TestSubmitRetryableEmptyEscrow(t *testing.T) { +func testSubmitRetryableEmptyEscrow(t *testing.T, arbosVersion uint64) { t.Parallel() - builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) + builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t, func(builder *NodeBuilder) { + builder.WithArbOSVersion(arbosVersion) + }) defer teardown() user2Address := builder.L2Info.GetAddress("User2") @@ -273,14 +283,20 @@ func TestSubmitRetryableEmptyEscrow(t *testing.T) { escrowAccount := retryables.RetryableEscrowAddress(l2Tx.Hash()) state, err := builder.L2.ExecNode.ArbInterface.BlockChain().State() Require(t, err) - escrowCodeHash := state.GetCodeHash(escrowAccount) - if escrowCodeHash == (common.Hash{}) { - Fatal(t, "Escrow account deleted (or not created)") - } else if escrowCodeHash != types.EmptyCodeHash { - Fatal(t, "Escrow account has unexpected code hash", escrowCodeHash) + escrowExists := state.Exist(escrowAccount) + if escrowExists != (arbosVersion < 30) { + Fatal(t, "Escrow account existance", escrowExists, "doesn't correspond to ArbOS version", arbosVersion) } } +func TestSubmitRetryableEmptyEscrowArbOS20(t *testing.T) { + testSubmitRetryableEmptyEscrow(t, 20) +} + +func TestSubmitRetryableEmptyEscrowArbOS30(t *testing.T) { + testSubmitRetryableEmptyEscrow(t, 30) +} + func TestSubmitRetryableFailThenRetry(t *testing.T) { t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) diff --git a/system_tests/seq_coordinator_test.go b/system_tests/seq_coordinator_test.go index 886a0528c..43d55f40c 100644 --- a/system_tests/seq_coordinator_test.go +++ b/system_tests/seq_coordinator_test.go @@ -8,12 +8,14 @@ import ( "errors" "fmt" "math/big" + "net" "testing" "time" "github.com/go-redis/redis/v8" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbostypes" @@ -21,6 +23,7 @@ import ( "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/testhelpers" ) func initRedisForTest(t *testing.T, ctx context.Context, redisUrl string, nodeNames []string) { @@ -270,6 +273,8 @@ func TestRedisSeqCoordinatorPriorities(t *testing.T) { } func testCoordinatorMessageSync(t *testing.T, successCase bool) { + logHandler := testhelpers.InitTestLog(t, log.LvlTrace) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -304,16 +309,25 @@ func testCoordinatorMessageSync(t *testing.T, successCase bool) { nodeConfigDup := *builder.nodeConfig builder.nodeConfig = &nodeConfigDup - + builder.nodeConfig.Feed.Output = *newBroadcasterConfigTest() builder.nodeConfig.SeqCoordinator.MyUrl = nodeNames[1] if !successCase { builder.nodeConfig.SeqCoordinator.Signer.ECDSA.AcceptSequencer = false builder.nodeConfig.SeqCoordinator.Signer.ECDSA.AllowedAddresses = []string{builder.L2Info.GetAddress("User2").Hex()} } - testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: builder.nodeConfig}) defer cleanupB() + // Build nodeBOutputFeedReader. + // nodeB doesn't sequence transactions, but adds messages related to them to its output feed. + // nodeBOutputFeedReader reads those messages from this feed and processes them. + // nodeBOutputFeedReader doesn't read messages from L1 since none of the nodes posts to L1. + nodeBPort := testClientB.ConsensusNode.BroadcastServer.ListenerAddr().(*net.TCPAddr).Port + nodeConfigNodeBOutputFeedReader := arbnode.ConfigDefaultL1NonSequencerTest() + nodeConfigNodeBOutputFeedReader.Feed.Input = *newBroadcastClientConfigTest(nodeBPort) + testClientNodeBOutputFeedReader, cleanupNodeBOutputFeedReader := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: nodeConfigNodeBOutputFeedReader}) + defer cleanupNodeBOutputFeedReader() + tx := builder.L2Info.PrepareTx("Owner", "User2", builder.L2Info.TransferGas, big.NewInt(1e12), nil) err = builder.L2.Client.SendTransaction(ctx, tx) @@ -330,6 +344,19 @@ func testCoordinatorMessageSync(t *testing.T, successCase bool) { if l2balance.Cmp(big.NewInt(1e12)) != 0 { t.Fatal("Unexpected balance:", l2balance) } + + // check that nodeBOutputFeedReader also processed the transaction + _, err = WaitForTx(ctx, testClientNodeBOutputFeedReader.Client, tx.Hash(), time.Second*5) + Require(t, err) + l2balance, err = testClientNodeBOutputFeedReader.Client.BalanceAt(ctx, builder.L2Info.GetAddress("User2"), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(1e12)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } + + if logHandler.WasLogged(arbnode.BlockHashMismatchLogMsg) { + t.Fatal("BlockHashMismatchLogMsg was logged unexpectedly") + } } else { _, err = WaitForTx(ctx, testClientB.Client, tx.Hash(), time.Second) if err == nil { diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 749a91e3b..ab30598b6 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -11,10 +11,19 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/broadcastclient" + "github.com/offchainlabs/nitro/broadcaster/backlog" + "github.com/offchainlabs/nitro/broadcaster/message" + "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/relay" "github.com/offchainlabs/nitro/util/signature" + "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/wsbroadcastserver" ) @@ -38,7 +47,8 @@ func newBroadcastClientConfigTest(port int) *broadcastclient.Config { } func TestSequencerFeed(t *testing.T) { - t.Parallel() + logHandler := testhelpers.InitTestLog(t, log.LvlTrace) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -73,6 +83,10 @@ func TestSequencerFeed(t *testing.T) { if l2balance.Cmp(big.NewInt(1e12)) != 0 { t.Fatal("Unexpected balance:", l2balance) } + + if logHandler.WasLogged(arbnode.BlockHashMismatchLogMsg) { + t.Fatal("BlockHashMismatchLogMsg was logged unexpectedly") + } } func TestRelayedSequencerFeed(t *testing.T) { @@ -250,3 +264,101 @@ func TestLyingSequencer(t *testing.T) { func TestLyingSequencerLocalDAS(t *testing.T) { testLyingSequencer(t, "files") } + +func testBlockHashComparison(t *testing.T, blockHash *common.Hash, mustMismatch bool) { + logHandler := testhelpers.InitTestLog(t, log.LvlTrace) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + backlogConfiFetcher := func() *backlog.Config { + return &backlog.DefaultTestConfig + } + bklg := backlog.NewBacklog(backlogConfiFetcher) + + wsBroadcastServer := wsbroadcastserver.NewWSBroadcastServer( + newBroadcasterConfigTest, + bklg, + 412346, + nil, + ) + err := wsBroadcastServer.Initialize() + if err != nil { + t.Fatal("error initializing wsBroadcastServer:", err) + } + err = wsBroadcastServer.Start(ctx) + if err != nil { + t.Fatal("error starting wsBroadcastServer:", err) + } + defer wsBroadcastServer.StopAndWait() + + port := wsBroadcastServer.ListenerAddr().(*net.TCPAddr).Port + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.nodeConfig.Feed.Input = *newBroadcastClientConfigTest(port) + cleanup := builder.Build(t) + defer cleanup() + testClient := builder.L2 + + userAccount := "User2" + builder.L2Info.GenerateAccount(userAccount) + tx := builder.L2Info.PrepareTx("Owner", userAccount, builder.L2Info.TransferGas, big.NewInt(1e12), nil) + l1IncomingMsgHeader := arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_L2Message, + Poster: l1pricing.BatchPosterAddress, + BlockNumber: 29, + Timestamp: 1715295980, + RequestId: nil, + L1BaseFee: nil, + } + l1IncomingMsg, err := gethexec.MessageFromTxes( + &l1IncomingMsgHeader, + types.Transactions{tx}, + []error{nil}, + ) + Require(t, err) + + broadcastMessage := message.BroadcastMessage{ + Version: 1, + Messages: []*message.BroadcastFeedMessage{ + { + SequenceNumber: 1, + Message: arbostypes.MessageWithMetadata{ + Message: l1IncomingMsg, + DelayedMessagesRead: 1, + }, + BlockHash: blockHash, + }, + }, + } + wsBroadcastServer.Broadcast(&broadcastMessage) + + // By now, even though block hash mismatch, the transaction should still be processed + _, err = WaitForTx(ctx, testClient.Client, tx.Hash(), time.Second*15) + if err != nil { + t.Fatal("error waiting for tx:", err) + } + l2balance, err := testClient.Client.BalanceAt(ctx, builder.L2Info.GetAddress(userAccount), nil) + if err != nil { + t.Fatal("error getting balance:", err) + } + if l2balance.Cmp(big.NewInt(1e12)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } + + mismatched := logHandler.WasLogged(arbnode.BlockHashMismatchLogMsg) + if mustMismatch && !mismatched { + t.Fatal("Failed to log BlockHashMismatchLogMsg") + } else if !mustMismatch && mismatched { + t.Fatal("BlockHashMismatchLogMsg was logged unexpectedly") + } +} + +func TestBlockHashFeedMismatch(t *testing.T) { + blockHash := common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") + testBlockHashComparison(t, &blockHash, true) +} + +func TestBlockHashFeedNil(t *testing.T) { + testBlockHashComparison(t, nil, false) +} diff --git a/system_tests/seqinbox_test.go b/system_tests/seqinbox_test.go index 006386eba..9890264d4 100644 --- a/system_tests/seqinbox_test.go +++ b/system_tests/seqinbox_test.go @@ -171,7 +171,7 @@ func testSequencerInboxReaderImpl(t *testing.T, validator bool) { var blockStates []blockTestState blockStates = append(blockStates, blockTestState{ balances: map[common.Address]*big.Int{ - ownerAddress: startOwnerBalance, + ownerAddress: startOwnerBalance.ToBig(), }, nonces: map[common.Address]uint64{ ownerAddress: startOwnerNonce, @@ -395,7 +395,7 @@ func testSequencerInboxReaderImpl(t *testing.T, validator bool) { } if batchCount.Cmp(big.NewInt(int64(len(blockStates)))) == 0 { break - } else if i >= 100 { + } else if i >= 140 { Fatal(t, "timed out waiting for l1 batch count update; have", batchCount, "want", len(blockStates)-1) } time.Sleep(10 * time.Millisecond) @@ -436,7 +436,7 @@ func testSequencerInboxReaderImpl(t *testing.T, validator bool) { Require(t, err) for acct, expectedBalance := range state.balances { haveBalance := stateDb.GetBalance(acct) - if expectedBalance.Cmp(haveBalance) < 0 { + if expectedBalance.Cmp(haveBalance.ToBig()) < 0 { Fatal(t, "unexpected balance for account", acct, "; expected", expectedBalance, "got", haveBalance) } } @@ -445,5 +445,6 @@ func testSequencerInboxReaderImpl(t *testing.T, validator bool) { } func TestSequencerInboxReader(t *testing.T) { + t.Skip("diagnose after Stylus merge") testSequencerInboxReaderImpl(t, false) } diff --git a/system_tests/snap_sync_test.go b/system_tests/snap_sync_test.go new file mode 100644 index 000000000..dd22bb027 --- /dev/null +++ b/system_tests/snap_sync_test.go @@ -0,0 +1,189 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbtest + +import ( + "context" + "math/big" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos/l2pricing" + "github.com/offchainlabs/nitro/util" +) + +func TestSnapSync(t *testing.T) { + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + + // 1st node with sequencer, stays up all the time. + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.L2Info = NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + cleanup := builder.Build(t) + defer cleanup() + + // 2nd node without sequencer, syncs up to the first node. + // This node will be stopped in middle and arbitrumdata will be deleted. + testDir := t.TempDir() + nodeBStack := createStackConfigForTest(testDir) + nodeB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + + builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) + + builder.L2Info.GenerateAccount("BackgroundUser") + + // Create transactions till batch count is 10 + createTransactionTillBatchCount(ctx, t, builder, 10) + // Wait for nodeB to sync up to the first node + waitForBlocksToCatchup(ctx, t, builder.L2.Client, nodeB.Client) + + // Create a config with snap sync enabled and same database directory as the 2nd node + nodeConfig := createNodeConfigWithSnapSync(t, builder) + // Cleanup the message data of 2nd node, but keep the block state data. + // This is to simulate a snap sync environment where we’ve just gotten the block state but don’t have any messages. + err := os.RemoveAll(nodeB.ConsensusNode.Stack.ResolvePath("arbitrumdata")) + Require(t, err) + + // Cleanup the 2nd node to release the database lock + cleanupB() + // New node with snap sync enabled, and the same database directory as the 2nd node but with no message data. + nodeC, cleanupC := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack, nodeConfig: nodeConfig}) + defer cleanupC() + + // Create transactions till batch count is 20 + createTransactionTillBatchCount(ctx, t, builder, 20) + // Wait for nodeB to sync up to the first node + waitForBatchCountToCatchup(ctx, t, builder.L2.ConsensusNode.InboxTracker, nodeC.ConsensusNode.InboxTracker) + // Once the node is synced up, check if the batch metadata is the same for the last batch + // This is to ensure that the snap sync worked correctly + count, err := builder.L2.ConsensusNode.InboxTracker.GetBatchCount() + Require(t, err) + metadata, err := builder.L2.ConsensusNode.InboxTracker.GetBatchMetadata(count - 1) + Require(t, err) + metadataNodeC, err := nodeC.ConsensusNode.InboxTracker.GetBatchMetadata(count - 1) + Require(t, err) + if metadata != metadataNodeC { + t.Error("Batch metadata mismatch") + } + finalMessageCount := uint64(metadata.MessageCount) + waitForBlockToCatchupToMessageCount(ctx, t, builder.L2.Client, finalMessageCount) + waitForBlockToCatchupToMessageCount(ctx, t, nodeC.Client, finalMessageCount) + // Fetching message count - 1 instead on the latest block number as the latest block number might not be + // present in the snap sync node since it does not have the sequencer feed. + header, err := builder.L2.Client.HeaderByNumber(ctx, big.NewInt(int64(finalMessageCount)-1)) + Require(t, err) + headerNodeC, err := nodeC.Client.HeaderByNumber(ctx, big.NewInt(int64(finalMessageCount)-1)) + Require(t, err) + // Once the node is synced up, check if the block hash is the same for the last block + // This is to ensure that the snap sync worked correctly + if header.Hash().Cmp(headerNodeC.Hash()) != 0 { + t.Error("Block hash mismatch") + } + // This to ensure that the node did a snap sync and did not sync the batch before the snap sync batch. + _, err = nodeC.ConsensusNode.InboxTracker.GetBatchMetadata(nodeConfig.SnapSyncTest.BatchCount - 3) + if err == nil { + t.Error("Batch metadata should not be present for the batch before the snap sync batch") + } +} + +func waitForBlockToCatchupToMessageCount( + ctx context.Context, + t *testing.T, + client *ethclient.Client, + finalMessageCount uint64, +) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(10 * time.Millisecond): + latestHeaderNodeC, err := client.HeaderByNumber(ctx, nil) + Require(t, err) + if latestHeaderNodeC.Number.Uint64() >= uint64(finalMessageCount)-1 { + return + } + } + } +} + +func waitForBlocksToCatchup(ctx context.Context, t *testing.T, clientA *ethclient.Client, clientB *ethclient.Client) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(10 * time.Millisecond): + headerA, err := clientA.HeaderByNumber(ctx, nil) + Require(t, err) + headerB, err := clientB.HeaderByNumber(ctx, nil) + Require(t, err) + if headerA.Number.Cmp(headerB.Number) == 0 { + return + } + } + } +} + +func waitForBatchCountToCatchup(ctx context.Context, t *testing.T, inboxTrackerA *arbnode.InboxTracker, inboxTrackerB *arbnode.InboxTracker) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(10 * time.Millisecond): + countA, err := inboxTrackerA.GetBatchCount() + Require(t, err) + countB, err := inboxTrackerB.GetBatchCount() + Require(t, err) + if countA == countB { + return + } + } + + } +} + +func createTransactionTillBatchCount(ctx context.Context, t *testing.T, builder *NodeBuilder, finalCount uint64) { + for { + Require(t, ctx.Err()) + tx := builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, big.NewInt(1), nil) + err := builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + count, err := builder.L2.ConsensusNode.InboxTracker.GetBatchCount() + Require(t, err) + if count > finalCount { + break + } + } +} + +func createNodeConfigWithSnapSync(t *testing.T, builder *NodeBuilder) *arbnode.Config { + batchCount, err := builder.L2.ConsensusNode.InboxTracker.GetBatchCount() + Require(t, err) + // Last batch is batchCount - 1, so prev batch is batchCount - 2 + prevBatchMetaData, err := builder.L2.ConsensusNode.InboxTracker.GetBatchMetadata(batchCount - 2) + Require(t, err) + prevMessage, err := builder.L2.ConsensusNode.TxStreamer.GetMessage(prevBatchMetaData.MessageCount - 1) + Require(t, err) + // Create a config with snap sync enabled and same database directory as the 2nd node + nodeConfig := builder.nodeConfig + nodeConfig.SnapSyncTest.Enabled = true + nodeConfig.SnapSyncTest.BatchCount = batchCount + nodeConfig.SnapSyncTest.DelayedCount = prevBatchMetaData.DelayedMessageCount - 1 + nodeConfig.SnapSyncTest.PrevDelayedRead = prevMessage.DelayedMessagesRead + nodeConfig.SnapSyncTest.PrevBatchMessageCount = uint64(prevBatchMetaData.MessageCount) + return nodeConfig +} diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 1fcc75425..4afe2e8cc 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -12,7 +12,6 @@ import ( "errors" "fmt" "math/big" - "net/http" "strings" "testing" "time" @@ -61,20 +60,10 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) t.Parallel() ctx, cancelCtx := context.WithCancel(context.Background()) defer cancelCtx() - httpSrv, srv := externalsignertest.NewServer(t) - cp, err := externalsignertest.CertPaths() - if err != nil { - t.Fatalf("Error getting cert paths: %v", err) - } - t.Cleanup(func() { - if err := httpSrv.Shutdown(ctx); err != nil { - t.Fatalf("Error shutting down http server: %v", err) - } - }) + srv := externalsignertest.NewServer(t) go func() { - log.Debug("Server is listening on port 1234...") - if err := httpSrv.ListenAndServeTLS(cp.ServerCert, cp.ServerKey); err != nil && err != http.ErrServerClosed { - log.Debug("ListenAndServeTLS() failed", "error", err) + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) return } }() @@ -208,8 +197,6 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) execNodeA, l2nodeA.ArbDB, nil, - nil, - nil, StaticFetcherFrom(t, &blockValidatorConfig), valStack, ) @@ -236,7 +223,7 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) } Require(t, err) cfg := arbnode.ConfigDefaultL1NonSequencerTest() - signerCfg, err := externalSignerTestCfg(srv.Address) + signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) if err != nil { t.Fatalf("Error getting external signer config: %v", err) } @@ -262,8 +249,6 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) execNodeB, l2nodeB.ArbDB, nil, - nil, - nil, StaticFetcherFrom(t, &blockValidatorConfig), valStack, ) diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 2c1143548..bb78bda48 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -26,6 +26,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/statetransfer" ) @@ -41,7 +42,7 @@ func BuildBlock( if lastBlockHeader != nil { delayedMessagesRead = lastBlockHeader.Nonce.Uint64() } - inboxMultiplexer := arbstate.NewInboxMultiplexer(inbox, delayedMessagesRead, nil, arbstate.KeysetValidate) + inboxMultiplexer := arbstate.NewInboxMultiplexer(inbox, delayedMessagesRead, nil, daprovider.KeysetValidate) ctx := context.Background() message, err := inboxMultiplexer.Pop(ctx) @@ -56,7 +57,7 @@ func BuildBlock( return seqBatch, nil } block, _, err := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher, + l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher, false, ) return block, err } @@ -121,7 +122,7 @@ func (c noopChainContext) GetHeader(common.Hash, uint64) *types.Header { func FuzzStateTransition(f *testing.F) { f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte) { - if len(seqMsg) > 0 && arbstate.IsL1AuthenticatedMessageHeaderByte(seqMsg[0]) { + if len(seqMsg) > 0 && daprovider.IsL1AuthenticatedMessageHeaderByte(seqMsg[0]) { return } chainDb := rawdb.NewMemoryDatabase() @@ -176,7 +177,7 @@ func FuzzStateTransition(f *testing.F) { binary.BigEndian.PutUint64(seqBatch[24:32], ^uint64(0)) binary.BigEndian.PutUint64(seqBatch[32:40], uint64(len(delayedMessages))) if compressSeqMsg { - seqBatch = append(seqBatch, arbstate.BrotliMessageHeaderByte) + seqBatch = append(seqBatch, daprovider.BrotliMessageHeaderByte) seqMsgCompressed, err := arbcompress.CompressLevel(seqMsg, 0) if err != nil { panic(fmt.Sprintf("failed to compress sequencer message: %v", err)) diff --git a/system_tests/staterecovery_test.go b/system_tests/staterecovery_test.go index ac30038cc..1dd4be244 100644 --- a/system_tests/staterecovery_test.go +++ b/system_tests/staterecovery_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/trie" + "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/staterecovery" "github.com/offchainlabs/nitro/execution/gethexec" ) @@ -49,10 +50,10 @@ func TestRectreateMissingStates(t *testing.T) { stack, err := node.New(builder.l2StackConfig) Require(t, err) defer stack.Close() - chainDb, err := stack.OpenDatabase("chaindb", 0, 0, "", false) + chainDb, err := stack.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) Require(t, err) defer chainDb.Close() - cacheConfig := gethexec.DefaultCacheConfigFor(stack, &gethexec.DefaultCachingConfig) + cacheConfig := gethexec.DefaultCacheConfigFor(stack, &gethexec.TestCachingConfig) bc, err := gethexec.GetBlockChain(chainDb, cacheConfig, builder.chainConfig, builder.execConfig.TxLookupLimit) Require(t, err) err = staterecovery.RecreateMissingStates(chainDb, bc, cacheConfig, 1) diff --git a/system_tests/stylus_test.go b/system_tests/stylus_test.go new file mode 100644 index 000000000..97f304119 --- /dev/null +++ b/system_tests/stylus_test.go @@ -0,0 +1,110 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//go:build stylustest && !race +// +build stylustest,!race + +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/core/vm" +) + +func TestProgramArbitratorKeccak(t *testing.T) { + keccakTest(t, false) +} + +func TestProgramArbitratorErrors(t *testing.T) { + errorTest(t, false) +} + +func TestProgramArbitratorStorage(t *testing.T) { + storageTest(t, false) +} + +func TestProgramArbitratorTransientStorage(t *testing.T) { + transientStorageTest(t, false) +} + +func TestProgramArbitratorMath(t *testing.T) { + fastMathTest(t, false) +} + +func TestProgramArbitratorCalls(t *testing.T) { + testCalls(t, false) +} + +func TestProgramArbitratorReturnData(t *testing.T) { + testReturnData(t, false) +} + +func TestProgramArbitratorLogs(t *testing.T) { + testLogs(t, false) +} + +func TestProgramArbitratorCreate(t *testing.T) { + testCreate(t, false) +} + +func TestProgramArbitratorEvmData(t *testing.T) { + testEvmData(t, false) +} + +func TestProgramArbitratorMemory(t *testing.T) { + testMemory(t, false) +} + +func TestProgramArbitratorActivateTwice(t *testing.T) { + t.Parallel() + testActivateTwice(t, false) +} + +func TestProgramArbitratorActivateFails(t *testing.T) { + t.Parallel() + testActivateFails(t, false) +} + +func TestProgramArbitratorEarlyExit(t *testing.T) { + testEarlyExit(t, false) +} + +func fullRecurseTest() [][]multiCallRecurse { + result := make([][]multiCallRecurse, 0) + for _, op0 := range []vm.OpCode{vm.SSTORE, vm.SLOAD} { + for _, contract0 := range []string{"multicall-rust", "multicall-evm"} { + for _, op1 := range []vm.OpCode{vm.CALL, vm.STATICCALL, vm.DELEGATECALL} { + for _, contract1 := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + for _, op2 := range []vm.OpCode{vm.CALL, vm.STATICCALL, vm.DELEGATECALL} { + for _, contract2 := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + for _, op3 := range []vm.OpCode{vm.CALL, vm.STATICCALL, vm.DELEGATECALL} { + for _, contract3 := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + recurse := make([]multiCallRecurse, 4) + recurse[0].opcode = op0 + recurse[0].Name = contract0 + recurse[1].opcode = op1 + recurse[1].Name = contract1 + recurse[2].opcode = op2 + recurse[2].Name = contract2 + recurse[3].opcode = op3 + recurse[3].Name = contract3 + result = append(result, recurse) + } + } + } + } + } + } + } + } + return result +} + +func TestProgramLongCall(t *testing.T) { + testProgramResursiveCalls(t, fullRecurseTest(), true) +} + +func TestProgramLongArbitratorCall(t *testing.T) { + testProgramResursiveCalls(t, fullRecurseTest(), false) +} diff --git a/system_tests/test_info.go b/system_tests/test_info.go index 4b8f4d87c..764a8ae39 100644 --- a/system_tests/test_info.go +++ b/system_tests/test_info.go @@ -216,6 +216,9 @@ func (b *BlockchainTestInfo) PrepareTxTo( b.T.Helper() info := b.GetInfoWithPrivKey(from) txNonce := atomic.AddUint64(&info.Nonce, 1) - 1 + if value == nil { + value = common.Big0 + } txData := &types.DynamicFeeTx{ To: to, Gas: gas, diff --git a/system_tests/transfer_test.go b/system_tests/transfer_test.go index a270cca76..a49e05935 100644 --- a/system_tests/transfer_test.go +++ b/system_tests/transfer_test.go @@ -4,10 +4,14 @@ package arbtest import ( + "bytes" "context" "fmt" "math/big" "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" ) func TestTransfer(t *testing.T) { @@ -36,3 +40,45 @@ func TestTransfer(t *testing.T) { Fatal(t, "Unexpected recipient balance: ", bal2) } } + +func TestP256Verify(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + for _, tc := range []struct { + desc string + initialVersion uint64 + want []byte + }{ + { + desc: "p256 should not be enabled on arbOS 20", + initialVersion: 20, + want: nil, + }, + { + desc: "p256 should be enabled on arbOS 20", + initialVersion: 30, + want: common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder.chainConfig.ArbitrumChainParams.InitialArbOSVersion = tc.initialVersion + cleanup := builder.Build(t) + defer cleanup() + addr := common.BytesToAddress([]byte{0x01, 0x00}) + got, err := builder.L2.Client.CallContract(ctx, ethereum.CallMsg{ + From: builder.L2Info.GetAddress("Owner"), + To: &addr, + Gas: builder.L2Info.TransferGas, + Data: common.Hex2Bytes("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"), + Value: big.NewInt(1e12), + }, nil) + if err != nil { + t.Fatalf("CallContract() unexpected error: %v", err) + } + if !bytes.Equal(got, tc.want) { + t.Errorf("P256Verify() = %v, want: %v", got, tc.want) + } + }) + } +} diff --git a/system_tests/triedb_race_test.go b/system_tests/triedb_race_test.go index 6d9415df8..9f14f0889 100644 --- a/system_tests/triedb_race_test.go +++ b/system_tests/triedb_race_test.go @@ -14,7 +14,7 @@ import ( ) func TestTrieDBCommitRace(t *testing.T) { - _ = testhelpers.InitTestLog(t, log.LvlError) + _ = testhelpers.InitTestLog(t, log.LevelError) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/validation_mock_test.go b/system_tests/validation_mock_test.go index d9c302b33..fb4f86857 100644 --- a/system_tests/validation_mock_test.go +++ b/system_tests/validation_mock_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" @@ -21,6 +22,9 @@ import ( "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_arb" + "github.com/offchainlabs/nitro/validator/valnode" + + validatorclient "github.com/offchainlabs/nitro/validator/client" ) type mockSpawner struct { @@ -52,6 +56,10 @@ func globalstateToTestPreimages(gs validator.GoGlobalState) map[common.Hash][]by return preimages } +func (s *mockSpawner) WasmModuleRoots() ([]common.Hash, error) { + return mockWasmModuleRoots, nil +} + func (s *mockSpawner) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { run := &mockValRun{ Promise: containers.NewPromise[validator.GoGlobalState](nil), @@ -62,12 +70,14 @@ func (s *mockSpawner) Launch(entry *validator.ValidationInput, moduleRoot common return run } -var mockWasmModuleRoot common.Hash = common.HexToHash("0xa5a5a5") +var mockWasmModuleRoots []common.Hash = []common.Hash{common.HexToHash("0xa5a5a5"), common.HexToHash("0x1212")} -func (s *mockSpawner) Start(context.Context) error { return nil } -func (s *mockSpawner) Stop() {} -func (s *mockSpawner) Name() string { return "mock" } -func (s *mockSpawner) Room() int { return 4 } +func (s *mockSpawner) Start(context.Context) error { + return nil +} +func (s *mockSpawner) Stop() {} +func (s *mockSpawner) Name() string { return "mock" } +func (s *mockSpawner) Room() int { return 4 } func (s *mockSpawner) CreateExecutionRun(wasmModuleRoot common.Hash, input *validator.ValidationInput) containers.PromiseInterface[validator.ExecutionRun] { s.ExecSpawned = append(s.ExecSpawned, input.Id) @@ -78,7 +88,7 @@ func (s *mockSpawner) CreateExecutionRun(wasmModuleRoot common.Hash, input *vali } func (s *mockSpawner) LatestWasmModuleRoot() containers.PromiseInterface[common.Hash] { - return containers.NewReadyPromise[common.Hash](mockWasmModuleRoot, nil) + return containers.NewReadyPromise[common.Hash](mockWasmModuleRoots[0], nil) } func (s *mockSpawner) WriteToFile(input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] { @@ -150,7 +160,7 @@ func createMockValidationNode(t *testing.T, ctx context.Context, config *server_ } configFetcher := func() *server_arb.ArbitratorSpawnerConfig { return config } spawner := &mockSpawner{} - serverAPI := server_api.NewExecutionServerAPI(spawner, spawner, configFetcher) + serverAPI := valnode.NewExecutionServerAPI(spawner, spawner, configFetcher) valAPIs := []rpc.API{{ Namespace: server_api.Namespace, @@ -181,17 +191,28 @@ func TestValidationServerAPI(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() _, validationDefault := createMockValidationNode(t, ctx, nil) - client := server_api.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), validationDefault) + client := validatorclient.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), validationDefault) err := client.Start(ctx) Require(t, err) wasmRoot, err := client.LatestWasmModuleRoot().Await(ctx) Require(t, err) - if wasmRoot != mockWasmModuleRoot { + if wasmRoot != mockWasmModuleRoots[0] { t.Error("unexpected mock wasmModuleRoot") } + roots, err := client.WasmModuleRoots() + Require(t, err) + if len(roots) != len(mockWasmModuleRoots) { + Fatal(t, "wrong number of wasmModuleRoots", len(roots)) + } + for i := range roots { + if roots[i] != mockWasmModuleRoots[i] { + Fatal(t, "unexpected root", roots[i], mockWasmModuleRoots[i]) + } + } + hash1 := common.HexToHash("0x11223344556677889900aabbccddeeff") hash2 := common.HexToHash("0x11111111122222223333333444444444") @@ -247,7 +268,7 @@ func TestValidationClientRoom(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() mockSpawner, spawnerStack := createMockValidationNode(t, ctx, nil) - client := server_api.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), spawnerStack) + client := validatorclient.NewExecutionClient(StaticFetcherFrom(t, &rpcclient.TestClientConfig), spawnerStack) err := client.Start(ctx) Require(t, err) @@ -334,10 +355,10 @@ func TestExecutionKeepAlive(t *testing.T) { _, validationShortTO := createMockValidationNode(t, ctx, &shortTimeoutConfig) configFetcher := StaticFetcherFrom(t, &rpcclient.TestClientConfig) - clientDefault := server_api.NewExecutionClient(configFetcher, validationDefault) + clientDefault := validatorclient.NewExecutionClient(configFetcher, validationDefault) err := clientDefault.Start(ctx) Require(t, err) - clientShortTO := server_api.NewExecutionClient(configFetcher, validationShortTO) + clientShortTO := validatorclient.NewExecutionClient(configFetcher, validationShortTO) err = clientShortTO.Start(ctx) Require(t, err) @@ -389,6 +410,7 @@ func (m *mockBlockRecorder) RecordBlockCreation( Pos: pos, BlockHash: res.BlockHash, Preimages: globalstateToTestPreimages(globalState), + UserWasms: make(state.UserWasms), }, nil } diff --git a/system_tests/wrap_transaction_test.go b/system_tests/wrap_transaction_test.go index 85cf01521..bd561ad5e 100644 --- a/system_tests/wrap_transaction_test.go +++ b/system_tests/wrap_transaction_test.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "math/big" + "testing" "time" "github.com/ethereum/go-ethereum" @@ -102,6 +103,21 @@ func EnsureTxSucceededWithTimeout(ctx context.Context, client arbutil.L1Interfac return receipt, arbutil.DetailTxError(ctx, client, tx, receipt) } +func EnsureTxFailed(t *testing.T, ctx context.Context, client arbutil.L1Interface, tx *types.Transaction) *types.Receipt { + t.Helper() + return EnsureTxFailedWithTimeout(t, ctx, client, tx, time.Second*5) +} + +func EnsureTxFailedWithTimeout(t *testing.T, ctx context.Context, client arbutil.L1Interface, tx *types.Transaction, timeout time.Duration) *types.Receipt { + t.Helper() + receipt, err := WaitForTx(ctx, client, tx.Hash(), timeout) + Require(t, err) + if receipt.Status != types.ReceiptStatusFailed { + Fatal(t, "unexpected succeess") + } + return receipt +} + func headerSubscribeMainLoop(chanOut chan<- *types.Header, ctx context.Context, client ethereum.ChainReader) { headerSubscription, err := client.SubscribeNewHead(ctx, chanOut) if err != nil { diff --git a/util/arbmath/bips.go b/util/arbmath/bips.go index 83c7a61ec..8b7c47d82 100644 --- a/util/arbmath/bips.go +++ b/util/arbmath/bips.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbmath @@ -6,8 +6,10 @@ package arbmath import "math/big" type Bips int64 +type UBips uint64 const OneInBips Bips = 10000 +const OneInUBips UBips = 10000 func NaturalToBips(natural int64) Bips { return Bips(SaturatingMul(natural, int64(OneInBips))) @@ -34,7 +36,15 @@ func UintMulByBips(value uint64, bips Bips) uint64 { } func SaturatingCastToBips(value uint64) Bips { - return Bips(SaturatingCast(value)) + return Bips(SaturatingCast[int64](value)) +} + +func (bips UBips) Uint64() uint64 { + return uint64(bips) +} + +func (bips Bips) Uint64() uint64 { + return uint64(bips) } // BigDivToBips returns dividend/divisor as bips, saturating if out of bounds diff --git a/util/arbmath/bits.go b/util/arbmath/bits.go index 89ce89e08..1b91e2755 100644 --- a/util/arbmath/bits.go +++ b/util/arbmath/bits.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" ) type bytes32 = common.Hash @@ -44,3 +45,96 @@ func Uint32ToBytes(value uint32) []byte { binary.BigEndian.PutUint32(result, value) return result } + +// Uint16ToBytes casts a uint16 to its big-endian representation +func Uint16ToBytes(value uint16) []byte { + result := make([]byte, 2) + binary.BigEndian.PutUint16(result, value) + return result +} + +// casts a uint8 to its big-endian representation +func Uint8ToBytes(value uint8) []byte { + return []byte{value} +} + +// casts a bool to its big-endian representation +func BoolToBytes(value bool) []byte { + if value { + return Uint8ToBytes(1) + } + return Uint8ToBytes(0) +} + +// BytesToUint creates a uint64 from its big-endian representation +func BytesToUint(value []byte) uint64 { + return binary.BigEndian.Uint64(value) +} + +// BytesToUint32 creates a uint32 from its big-endian representation +func BytesToUint32(value []byte) uint32 { + return binary.BigEndian.Uint32(value) +} + +// BytesToUint16 creates a uint16 from its big-endian representation +func BytesToUint16(value []byte) uint16 { + return binary.BigEndian.Uint16(value) +} + +// creates a uint8 from its big-endian representation +func BytesToUint8(value []byte) uint8 { + return value[0] +} + +// creates a uint256 from its big-endian representation +func BytesToUint256(value []byte) *uint256.Int { + int := &uint256.Int{} + int.SetBytes(value) + return int +} + +// creates a bool from its big-endian representation +func BytesToBool(value []byte) bool { + return value[0] != 0 +} + +// BoolToUint8 assigns a nonzero value when true +func BoolToUint8(value bool) uint8 { + if value { + return 1 + } + return 0 +} + +// BoolToUint32 assigns a nonzero value when true +func BoolToUint32(value bool) uint32 { + if value { + return 1 + } + return 0 +} + +// BoolToUint32 assigns a nonzero value when true +func UintToBool[T Unsigned](value T) bool { + return value != 0 +} + +// Ensures a slice is non-nil +func NonNilSlice[T any](slice []T) []T { + if slice == nil { + return []T{} + } + return slice +} + +// Equivalent to slice[start:offset], but truncates when out of bounds rather than panicking. +func SliceWithRunoff[S any, I Integer](slice []S, start, end I) []S { + len := I(len(slice)) + start = MaxInt(start, 0) + end = MaxInt(start, end) + + if slice == nil || start >= len { + return []S{} + } + return slice[start:MinInt(end, len)] +} diff --git a/util/arbmath/math.go b/util/arbmath/math.go index eaac79bfa..741395540 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE package arbmath @@ -7,6 +7,7 @@ import ( "math" "math/big" "math/bits" + "unsafe" eth_math "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" @@ -61,12 +62,28 @@ func MinInt[T Number](value, ceiling T) T { return value } -// MaxInt the maximum of two ints -func MaxInt[T Number](value, floor T) T { - if value < floor { - return floor +// MaxInt the maximum of one or more ints +func MaxInt[T Number](values ...T) T { + max := values[0] + for i := 1; i < len(values); i++ { + value := values[i] + if value > max { + max = value + } } - return value + return max +} + +// Checks if two ints are sufficiently close to one another +func Within[T Unsigned](a, b, bound T) bool { + min := MinInt(a, b) + max := MaxInt(a, b) + return max-min <= bound +} + +// Checks if an int belongs to [a, b] +func WithinRange[T Unsigned](value, a, b T) bool { + return a <= value && value <= b } // UintToBig casts an int to a huge @@ -133,6 +150,11 @@ func BigGreaterThan(first, second *big.Int) bool { return first.Cmp(second) > 0 } +// BigGreaterThanOrEqual check if a huge is greater than or equal to another +func BigGreaterThanOrEqual(first, second *big.Int) bool { + return first.Cmp(second) >= 0 +} + // BigMin returns a clone of the minimum of two big integers func BigMin(first, second *big.Int) *big.Int { if BigLessThan(first, second) { @@ -237,76 +259,104 @@ func BigFloatMulByUint(multiplicand *big.Float, multiplier uint64) *big.Float { return new(big.Float).Mul(multiplicand, UintToBigFloat(multiplier)) } -// SaturatingAdd add two int64's without overflow -func SaturatingAdd(augend, addend int64) int64 { - sum := augend + addend - if addend > 0 && sum < augend { - sum = math.MaxInt64 +func MaxSignedValue[T Signed]() T { + return T((uint64(1) << (8*unsafe.Sizeof(T(0)) - 1)) - 1) +} + +func MinSignedValue[T Signed]() T { + return T(uint64(1) << ((8 * unsafe.Sizeof(T(0))) - 1)) +} + +// SaturatingAdd add two integers without overflow +func SaturatingAdd[T Signed](a, b T) T { + sum := a + b + if b > 0 && sum < a { + sum = MaxSignedValue[T]() } - if addend < 0 && sum > augend { - sum = math.MinInt64 + if b < 0 && sum > a { + sum = MinSignedValue[T]() } return sum } -// SaturatingUAdd add two uint64's without overflow -func SaturatingUAdd(augend uint64, addend uint64) uint64 { - sum := augend + addend - if sum < augend || sum < addend { - sum = math.MaxUint64 +// SaturatingUAdd add two integers without overflow +func SaturatingUAdd[T Unsigned](a, b T) T { + sum := a + b + if sum < a || sum < b { + sum = ^T(0) } return sum } // SaturatingSub subtract an int64 from another without overflow func SaturatingSub(minuend, subtrahend int64) int64 { - return SaturatingAdd(minuend, -subtrahend) + if subtrahend == math.MinInt64 { + // The absolute value of MinInt64 is one greater than MaxInt64 + return SaturatingAdd(SaturatingAdd(minuend, math.MaxInt64), 1) + } + return SaturatingAdd(minuend, SaturatingNeg(subtrahend)) } -// SaturatingUSub subtract a uint64 from another without underflow -func SaturatingUSub(minuend uint64, subtrahend uint64) uint64 { - if subtrahend >= minuend { +// SaturatingUSub subtract an integer from another without underflow +func SaturatingUSub[T Unsigned](a, b T) T { + if b >= a { return 0 } - return minuend - subtrahend + return a - b } -// SaturatingUMul multiply two uint64's without overflow -func SaturatingUMul(multiplicand uint64, multiplier uint64) uint64 { - product := multiplicand * multiplier - if multiplier != 0 && product/multiplier != multiplicand { - product = math.MaxUint64 +// SaturatingUMul multiply two integers without over/underflow +func SaturatingUMul[T Unsigned](a, b T) T { + product := a * b + if b != 0 && product/b != a { + product = ^T(0) } return product } -// SaturatingMul multiply two int64's without over/underflow -func SaturatingMul(multiplicand int64, multiplier int64) int64 { - product := multiplicand * multiplier - if multiplier != 0 && product/multiplier != multiplicand { - if (multiplicand > 0 && multiplier > 0) || (multiplicand < 0 && multiplier < 0) { - product = math.MaxInt64 +// SaturatingMul multiply two integers without over/underflow +func SaturatingMul[T Signed](a, b T) T { + product := a * b + if b != 0 && product/b != a { + if (a > 0 && b > 0) || (a < 0 && b < 0) { + product = MaxSignedValue[T]() } else { - product = math.MinInt64 + product = MinSignedValue[T]() } } return product } -// SaturatingCast cast a uint64 to an int64, clipping to [0, 2^63-1] -func SaturatingCast(value uint64) int64 { - if value > math.MaxInt64 { - return math.MaxInt64 +// SaturatingCast cast an unsigned integer to a signed one, clipping to [0, S::MAX] +func SaturatingCast[S Signed, T Unsigned](value T) S { + tBig := unsafe.Sizeof(T(0)) >= unsafe.Sizeof(S(0)) + bits := uint64(8 * unsafe.Sizeof(S(0))) + sMax := T(1<> 1 + if tBig && value > sMax { + return S(sMax) } - return int64(value) + return S(value) } -// SaturatingUCast cast an int64 to a uint64, clipping to [0, 2^63-1] -func SaturatingUCast(value int64) uint64 { - if value < 0 { +// SaturatingUCast cast a signed integer to an unsigned one, clipping to [0, T::MAX] +func SaturatingUCast[T Unsigned, S Signed](value S) T { + if value <= 0 { return 0 } - return uint64(value) + tSmall := unsafe.Sizeof(T(0)) < unsafe.Sizeof(S(0)) + if tSmall && value >= S(^T(0)) { + return ^T(0) + } + return T(value) +} + +// SaturatingUUCast cast an unsigned integer to another, clipping to [0, U::MAX] +func SaturatingUUCast[U, T Unsigned](value T) U { + tBig := unsafe.Sizeof(T(0)) > unsafe.Sizeof(U(0)) + if tBig && value > T(^U(0)) { + return ^U(0) + } + return U(value) } func SaturatingCastToUint(value *big.Int) uint64 { @@ -319,25 +369,42 @@ func SaturatingCastToUint(value *big.Int) uint64 { return value.Uint64() } +// Negates an int without underflow +func SaturatingNeg[T Signed](value T) T { + if value < 0 && value == MinSignedValue[T]() { + return MaxSignedValue[T]() + } + return -value +} + +// Integer division but rounding up +func DivCeil[T Unsigned](value, divisor T) T { + if value%divisor == 0 { + return value / divisor + } + return value/divisor + 1 +} + // ApproxExpBasisPoints return the Maclaurin series approximation of e^x, where x is denominated in basis points. -// This quartic polynomial will underestimate e^x by about 5% as x approaches 20000 bips. -func ApproxExpBasisPoints(value Bips) Bips { +// The quartic polynomial will underestimate e^x by about 5% as x approaches 20000 bips. +func ApproxExpBasisPoints(value Bips, degree uint64) Bips { input := value negative := value < 0 if negative { input = -value } x := uint64(input) - bips := uint64(OneInBips) - res := bips + x/4 - res = bips + SaturatingUMul(res, x)/(3*bips) - res = bips + SaturatingUMul(res, x)/(2*bips) - res = bips + SaturatingUMul(res, x)/(1*bips) + + res := bips + x/degree + for i := uint64(1); i < degree; i++ { + res = bips + SaturatingUMul(res, x)/((degree-i)*bips) + } + if negative { - return Bips(SaturatingCast(bips * bips / res)) + return Bips(SaturatingCast[int64](bips * bips / res)) } else { - return Bips(SaturatingCast(res)) + return Bips(SaturatingCast[int64](res)) } } diff --git a/util/arbmath/math_fuzz_test.go b/util/arbmath/math_fuzz_test.go new file mode 100644 index 000000000..591d699de --- /dev/null +++ b/util/arbmath/math_fuzz_test.go @@ -0,0 +1,112 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbmath + +import ( + "math/big" + "testing" +) + +func toBig[T Signed](a T) *big.Int { + return big.NewInt(int64(a)) +} + +func saturatingBigToInt[T Signed](a *big.Int) T { + // MinSignedValue and MaxSignedValue are already separately tested + if a.Cmp(toBig(MaxSignedValue[T]())) > 0 { + return MaxSignedValue[T]() + } + if a.Cmp(toBig(MinSignedValue[T]())) < 0 { + return MinSignedValue[T]() + } + return T(a.Int64()) +} + +func fuzzSaturatingAdd[T Signed](f *testing.F) { + f.Fuzz(func(t *testing.T, a, b T) { + got := SaturatingAdd(a, b) + expected := saturatingBigToInt[T](new(big.Int).Add(toBig(a), toBig(b))) + if got != expected { + t.Errorf("SaturatingAdd(%v, %v) = %v, expected %v", a, b, got, expected) + } + }) +} + +func fuzzSaturatingMul[T Signed](f *testing.F) { + f.Fuzz(func(t *testing.T, a, b T) { + got := SaturatingMul(a, b) + expected := saturatingBigToInt[T](new(big.Int).Mul(toBig(a), toBig(b))) + if got != expected { + t.Errorf("SaturatingMul(%v, %v) = %v, expected %v", a, b, got, expected) + } + }) +} + +func fuzzSaturatingNeg[T Signed](f *testing.F) { + f.Fuzz(func(t *testing.T, a T) { + got := SaturatingNeg(a) + expected := saturatingBigToInt[T](new(big.Int).Neg(toBig(a))) + if got != expected { + t.Errorf("SaturatingNeg(%v) = %v, expected %v", a, got, expected) + } + }) +} + +func FuzzSaturatingAddInt8(f *testing.F) { + fuzzSaturatingAdd[int8](f) +} + +func FuzzSaturatingAddInt16(f *testing.F) { + fuzzSaturatingAdd[int16](f) +} + +func FuzzSaturatingAddInt32(f *testing.F) { + fuzzSaturatingAdd[int32](f) +} + +func FuzzSaturatingAddInt64(f *testing.F) { + fuzzSaturatingAdd[int64](f) +} + +func FuzzSaturatingSub(f *testing.F) { + f.Fuzz(func(t *testing.T, a, b int64) { + got := SaturatingSub(a, b) + expected := saturatingBigToInt[int64](new(big.Int).Sub(toBig(a), toBig(b))) + if got != expected { + t.Errorf("SaturatingSub(%v, %v) = %v, expected %v", a, b, got, expected) + } + }) +} + +func FuzzSaturatingMulInt8(f *testing.F) { + fuzzSaturatingMul[int8](f) +} + +func FuzzSaturatingMulInt16(f *testing.F) { + fuzzSaturatingMul[int16](f) +} + +func FuzzSaturatingMulInt32(f *testing.F) { + fuzzSaturatingMul[int32](f) +} + +func FuzzSaturatingMulInt64(f *testing.F) { + fuzzSaturatingMul[int64](f) +} + +func FuzzSaturatingNegInt8(f *testing.F) { + fuzzSaturatingNeg[int8](f) +} + +func FuzzSaturatingNegInt16(f *testing.F) { + fuzzSaturatingNeg[int16](f) +} + +func FuzzSaturatingNegInt32(f *testing.F) { + fuzzSaturatingNeg[int32](f) +} + +func FuzzSaturatingNegInt64(f *testing.F) { + fuzzSaturatingNeg[int64](f) +} diff --git a/util/arbmath/math_test.go b/util/arbmath/math_test.go index 7bb643f91..1be60dc58 100644 --- a/util/arbmath/math_test.go +++ b/util/arbmath/math_test.go @@ -1,13 +1,16 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbmath import ( + "bytes" + "fmt" "math" "math/rand" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/util/testhelpers" ) @@ -59,6 +62,167 @@ func TestMath(t *testing.T) { Fail(t, "incorrect", "2^", i, diff, approx, correct) } } + + assert := func(cond bool) { + t.Helper() + if !cond { + Fail(t) + } + } + assert(uint64(math.MaxInt64) == SaturatingUCast[uint64](int64(math.MaxInt64))) + assert(uint64(math.MaxInt64-1) == SaturatingUCast[uint64](int64(math.MaxInt64-1))) + assert(uint64(math.MaxInt64-1) == SaturatingUCast[uint64](math.MaxInt64-1)) + assert(uint64(math.MaxInt64) == SaturatingUCast[uint64](math.MaxInt64)) + assert(uint32(math.MaxUint32) == SaturatingUCast[uint32](math.MaxInt64-1)) + assert(uint16(math.MaxUint16) == SaturatingUCast[uint16](math.MaxInt32)) + assert(uint16(math.MaxUint16) == SaturatingUCast[uint16](math.MaxInt32-1)) + assert(uint16(math.MaxUint16) == SaturatingUCast[uint16](math.MaxInt-1)) + assert(uint8(math.MaxUint8) == SaturatingUCast[uint8](math.MaxInt-1)) + assert(uint(math.MaxInt-1) == SaturatingUCast[uint](math.MaxInt-1)) + assert(uint(math.MaxInt-1) == SaturatingUCast[uint](int64(math.MaxInt-1))) + + assert(int64(math.MaxInt64) == SaturatingCast[int64, uint64](math.MaxUint64)) + assert(int64(math.MaxInt64) == SaturatingCast[int64, uint64](math.MaxUint64-1)) + assert(int32(math.MaxInt32) == SaturatingCast[int32, uint64](math.MaxUint64)) + assert(int32(math.MaxInt32) == SaturatingCast[int32, uint64](math.MaxUint64-1)) + assert(int8(math.MaxInt8) == SaturatingCast[int8, uint16](math.MaxUint16)) + assert(int8(32) == SaturatingCast[int8, uint16](32)) + assert(int16(0) == SaturatingCast[int16, uint32](0)) + assert(int16(math.MaxInt16) == SaturatingCast[int16, uint32](math.MaxInt16)) + assert(int16(math.MaxInt16) == SaturatingCast[int16, uint16](math.MaxInt16)) + assert(int16(math.MaxInt8) == SaturatingCast[int16, uint8](math.MaxInt8)) + + assert(uint32(math.MaxUint32) == SaturatingUUCast[uint32, uint64](math.MaxUint64)) + assert(uint32(math.MaxUint16) == SaturatingUUCast[uint32, uint64](math.MaxUint16)) + assert(uint32(math.MaxUint16) == SaturatingUUCast[uint32, uint16](math.MaxUint16)) + assert(uint16(math.MaxUint16) == SaturatingUUCast[uint16, uint16](math.MaxUint16)) +} + +func TestSlices(t *testing.T) { + assert_eq := func(left, right []uint8) { + t.Helper() + if !bytes.Equal(left, right) { + Fail(t, common.Bytes2Hex(left), " ", common.Bytes2Hex(right)) + } + } + + data := []uint8{0, 1, 2, 3} + assert_eq(SliceWithRunoff(data, 4, 4), data[0:0]) + assert_eq(SliceWithRunoff(data, 1, 0), data[0:0]) + assert_eq(SliceWithRunoff(data, 0, 0), data[0:0]) + assert_eq(SliceWithRunoff(data, 0, 1), data[0:1]) + assert_eq(SliceWithRunoff(data, 1, 3), data[1:3]) + assert_eq(SliceWithRunoff(data, 0, 4), data[0:4]) + assert_eq(SliceWithRunoff(data, 0, 5), data[0:4]) + assert_eq(SliceWithRunoff(data, 2, math.MaxUint8), data[2:4]) + + assert_eq(SliceWithRunoff(data, -1, -2), []uint8{}) + assert_eq(SliceWithRunoff(data, 5, 3), []uint8{}) + assert_eq(SliceWithRunoff(data, 7, 8), []uint8{}) +} + +func testMinMaxSignedValues[T Signed](t *testing.T, min T, max T) { + gotMin := MinSignedValue[T]() + if gotMin != min { + Fail(t, "expected min", min, "but got", gotMin) + } + gotMax := MaxSignedValue[T]() + if gotMax != max { + Fail(t, "expected max", max, "but got", gotMax) + } +} + +func TestMinMaxSignedValues(t *testing.T) { + testMinMaxSignedValues[int8](t, math.MinInt8, math.MaxInt8) + testMinMaxSignedValues[int16](t, math.MinInt16, math.MaxInt16) + testMinMaxSignedValues[int32](t, math.MinInt32, math.MaxInt32) + testMinMaxSignedValues[int64](t, math.MinInt64, math.MaxInt64) +} + +func TestSaturatingAdd(t *testing.T) { + tests := []struct { + a, b, expected int64 + }{ + {2, 3, 5}, + {-1, -2, -3}, + {math.MaxInt64, 1, math.MaxInt64}, + {math.MaxInt64, math.MaxInt64, math.MaxInt64}, + {math.MinInt64, -1, math.MinInt64}, + {math.MinInt64, math.MinInt64, math.MinInt64}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("%v + %v = %v", tc.a, tc.b, tc.expected), func(t *testing.T) { + sum := SaturatingAdd(int64(tc.a), int64(tc.b)) + if sum != tc.expected { + t.Errorf("SaturatingAdd(%v, %v) = %v; want %v", tc.a, tc.b, sum, tc.expected) + } + }) + } +} + +func TestSaturatingSub(t *testing.T) { + tests := []struct { + a, b, expected int64 + }{ + {5, 3, 2}, + {-3, -2, -1}, + {math.MinInt64, 1, math.MinInt64}, + {math.MinInt64, -1, math.MinInt64 + 1}, + {math.MinInt64, math.MinInt64, 0}, + {0, math.MinInt64, math.MaxInt64}, + } + + for _, tc := range tests { + t.Run("", func(t *testing.T) { + sum := SaturatingSub(int64(tc.a), int64(tc.b)) + if sum != tc.expected { + t.Errorf("SaturatingSub(%v, %v) = %v; want %v", tc.a, tc.b, sum, tc.expected) + } + }) + } +} + +func TestSaturatingMul(t *testing.T) { + tests := []struct { + a, b, expected int64 + }{ + {5, 3, 15}, + {-3, -2, 6}, + {math.MaxInt64, 2, math.MaxInt64}, + {math.MinInt64, 2, math.MinInt64}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("%v - %v = %v", tc.a, tc.b, tc.expected), func(t *testing.T) { + sum := SaturatingMul(int64(tc.a), int64(tc.b)) + if sum != tc.expected { + t.Errorf("SaturatingMul(%v, %v) = %v; want %v", tc.a, tc.b, sum, tc.expected) + } + }) + } +} + +func TestSaturatingNeg(t *testing.T) { + tests := []struct { + value int64 + expected int64 + }{ + {0, 0}, + {5, -5}, + {-5, 5}, + {math.MinInt64, math.MaxInt64}, + {math.MaxInt64, math.MinInt64 + 1}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("-%v = %v", tc.value, tc.expected), func(t *testing.T) { + result := SaturatingNeg(tc.value) + if result != tc.expected { + t.Errorf("SaturatingNeg(%v) = %v: expected %v", tc.value, result, tc.expected) + } + }) + } } func Fail(t *testing.T, printables ...interface{}) { diff --git a/util/arbmath/time.go b/util/arbmath/time.go new file mode 100644 index 000000000..af7d9ae84 --- /dev/null +++ b/util/arbmath/time.go @@ -0,0 +1,8 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package arbmath + +func DaysToSeconds[T Unsigned](days T) uint64 { + return uint64(days) * 24 * 60 * 60 +} diff --git a/util/arbmath/uint24.go b/util/arbmath/uint24.go new file mode 100644 index 000000000..818f871a2 --- /dev/null +++ b/util/arbmath/uint24.go @@ -0,0 +1,57 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package arbmath + +import ( + "encoding/binary" + "errors" + "math/big" +) + +const MaxUint24 = 1<<24 - 1 // 16777215 + +type Uint24 uint32 + +func (value Uint24) ToBig() *big.Int { + return UintToBig(uint64(value)) +} + +func (value Uint24) ToUint32() uint32 { + return uint32(value) +} + +func (value Uint24) ToUint64() uint64 { + return uint64(value) +} + +func IntToUint24[T uint32 | uint64](value T) (Uint24, error) { + if value > T(MaxUint24) { + return Uint24(MaxUint24), errors.New("value out of range") + } + return Uint24(value), nil +} + +// Casts a huge to a uint24, panicking if out of bounds +func BigToUint24OrPanic(value *big.Int) Uint24 { + if value.Sign() < 0 { + panic("big.Int value is less than 0") + } + if !value.IsUint64() || value.Uint64() > MaxUint24 { + panic("big.Int value exceeds the max Uint24") + } + return Uint24(value.Uint64()) +} + +// creates a uint24 from its big-endian representation +func BytesToUint24(value []byte) Uint24 { + value32 := ConcatByteSlices([]byte{0}, value) + return Uint24(binary.BigEndian.Uint32(value32)) +} + +// casts a uint24 to its big-endian representation +func Uint24ToBytes(value Uint24) []byte { + result := make([]byte, 4) + binary.BigEndian.PutUint32(result, value.ToUint32()) + return result[1:] +} diff --git a/util/colors/colors.go b/util/colors/colors.go index 56d8b51d1..c652d80ca 100644 --- a/util/colors/colors.go +++ b/util/colors/colors.go @@ -3,7 +3,10 @@ package colors -import "fmt" +import ( + "fmt" + "regexp" +) var Red = "\033[31;1m" var Blue = "\033[34;1m" @@ -48,3 +51,17 @@ func PrintYellow(args ...interface{}) { fmt.Print(args...) println(Clear) } + +func PrintPink(args ...interface{}) { + print(Pink) + fmt.Print(args...) + println(Clear) +} + +func Uncolor(text string) string { + uncolor := regexp.MustCompile("\x1b\\[([0-9]+;)*[0-9]+m") + unwhite := regexp.MustCompile(`\s+`) + + text = uncolor.ReplaceAllString(text, "") + return unwhite.ReplaceAllString(text, " ") +} diff --git a/util/headerreader/blob_client.go b/util/headerreader/blob_client.go index 664dbb5e3..2b47a940c 100644 --- a/util/headerreader/blob_client.go +++ b/util/headerreader/blob_client.go @@ -13,6 +13,7 @@ import ( "net/url" "os" "path" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -188,8 +189,14 @@ const trailingCharsOfResponse = 25 func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHashes []common.Hash) ([]kzg4844.Blob, error) { rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot)) - if err != nil { - return nil, fmt.Errorf("error calling beacon client in blobSidecars: %w", err) + if err != nil || len(rawData) == 0 { + // blobs are pruned after 4096 epochs (1 epoch = 32 slots), we determine if the requested slot were to be pruned by a non-archive endpoint + roughAgeOfSlot := uint64(time.Now().Unix()) - (b.genesisTime + slot*b.secondsPerSlot) + if roughAgeOfSlot > b.secondsPerSlot*32*4096 { + return nil, fmt.Errorf("beacon client in blobSidecars got error or empty response fetching older blobs in slot: %d, an archive endpoint is required, please refer to https://docs.arbitrum.io/run-arbitrum-node/l1-ethereum-beacon-chain-rpc-providers, err: %w", slot, err) + } else { + return nil, fmt.Errorf("beacon client in blobSidecars got error or empty response fetching non-expired blobs in slot: %d, if using a prysm endpoint, try --enable-experimental-backfill flag, err: %w", slot, err) + } } var response []blobResponseItem if err := json.Unmarshal(rawData, &response); err != nil { @@ -222,10 +229,11 @@ func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHas var found bool for outputIdx = range versionedHashes { if versionedHashes[outputIdx] == versionedHash { - found = true if outputsFound[outputIdx] { - return nil, fmt.Errorf("found blob with versioned hash %v twice", versionedHash) + // Duplicate, skip this one + break } + found = true outputsFound[outputIdx] = true break } diff --git a/util/rpcclient/rpcclient.go b/util/rpcclient/rpcclient.go index 02b41cf15..56aebef39 100644 --- a/util/rpcclient/rpcclient.go +++ b/util/rpcclient/rpcclient.go @@ -21,14 +21,15 @@ import ( ) type ClientConfig struct { - URL string `json:"url,omitempty" koanf:"url"` - JWTSecret string `json:"jwtsecret,omitempty" koanf:"jwtsecret"` - Timeout time.Duration `json:"timeout,omitempty" koanf:"timeout" reload:"hot"` - Retries uint `json:"retries,omitempty" koanf:"retries" reload:"hot"` - ConnectionWait time.Duration `json:"connection-wait,omitempty" koanf:"connection-wait"` - ArgLogLimit uint `json:"arg-log-limit,omitempty" koanf:"arg-log-limit" reload:"hot"` - RetryErrors string `json:"retry-errors,omitempty" koanf:"retry-errors" reload:"hot"` - RetryDelay time.Duration `json:"retry-delay,omitempty" koanf:"retry-delay"` + URL string `json:"url,omitempty" koanf:"url"` + JWTSecret string `json:"jwtsecret,omitempty" koanf:"jwtsecret"` + Timeout time.Duration `json:"timeout,omitempty" koanf:"timeout" reload:"hot"` + Retries uint `json:"retries,omitempty" koanf:"retries" reload:"hot"` + ConnectionWait time.Duration `json:"connection-wait,omitempty" koanf:"connection-wait"` + ArgLogLimit uint `json:"arg-log-limit,omitempty" koanf:"arg-log-limit" reload:"hot"` + RetryErrors string `json:"retry-errors,omitempty" koanf:"retry-errors" reload:"hot"` + RetryDelay time.Duration `json:"retry-delay,omitempty" koanf:"retry-delay"` + WebsocketMessageSizeLimit int64 `json:"websocket-message-size-limit,omitempty" koanf:"websocket-message-size-limit"` retryErrors *regexp.Regexp } @@ -46,16 +47,18 @@ func (c *ClientConfig) Validate() error { type ClientConfigFetcher func() *ClientConfig var TestClientConfig = ClientConfig{ - URL: "self", - JWTSecret: "", + URL: "self", + JWTSecret: "", + WebsocketMessageSizeLimit: 256 * 1024 * 1024, } var DefaultClientConfig = ClientConfig{ - URL: "self-auth", - JWTSecret: "", - Retries: 3, - RetryErrors: "websocket: close.*|dial tcp .*|.*i/o timeout|.*connection reset by peer|.*connection refused", - ArgLogLimit: 2048, + URL: "self-auth", + JWTSecret: "", + Retries: 3, + RetryErrors: "websocket: close.*|dial tcp .*|.*i/o timeout|.*connection reset by peer|.*connection refused", + ArgLogLimit: 2048, + WebsocketMessageSizeLimit: 256 * 1024 * 1024, } func RPCClientAddOptions(prefix string, f *flag.FlagSet, defaultConfig *ClientConfig) { @@ -67,6 +70,7 @@ func RPCClientAddOptions(prefix string, f *flag.FlagSet, defaultConfig *ClientCo f.Uint(prefix+".retries", defaultConfig.Retries, "number of retries in case of failure(0 mean one attempt)") f.String(prefix+".retry-errors", defaultConfig.RetryErrors, "Errors matching this regular expression are automatically retried") f.Duration(prefix+".retry-delay", defaultConfig.RetryDelay, "delay between retries") + f.Int64(prefix+".websocket-message-size-limit", defaultConfig.WebsocketMessageSizeLimit, "websocket message size limit used by the RPC client. 0 means no limit") } type RpcClient struct { @@ -256,9 +260,9 @@ func (c *RpcClient) Start(ctx_in context.Context) error { var err error var client *rpc.Client if jwt == nil { - client, err = rpc.DialContext(ctx, url) + client, err = rpc.DialOptions(ctx, url, rpc.WithWebsocketMessageSizeLimit(c.config().WebsocketMessageSizeLimit)) } else { - client, err = rpc.DialOptions(ctx, url, rpc.WithHTTPAuth(node.NewJWTAuth([32]byte(*jwt)))) + client, err = rpc.DialOptions(ctx, url, rpc.WithHTTPAuth(node.NewJWTAuth([32]byte(*jwt))), rpc.WithWebsocketMessageSizeLimit(c.config().WebsocketMessageSizeLimit)) } cancelCtx() if err == nil { diff --git a/util/testhelpers/port.go b/util/testhelpers/port.go new file mode 100644 index 000000000..d31fa41cd --- /dev/null +++ b/util/testhelpers/port.go @@ -0,0 +1,17 @@ +package testhelpers + +import ( + "net" +) + +// FreeTCPPortListener returns a listener listening on an unused local port. +// +// This is useful for tests that need to bind to a port without risking a conflict. +func FreeTCPPortListener() (net.Listener, error) { + // This works because the kernel will assign an unused port when ":0" is opened. + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + return l, nil +} diff --git a/util/testhelpers/port_test.go b/util/testhelpers/port_test.go new file mode 100644 index 000000000..ef9bb1853 --- /dev/null +++ b/util/testhelpers/port_test.go @@ -0,0 +1,23 @@ +package testhelpers + +import ( + "net" + "testing" +) + +func TestFreeTCPPortListener(t *testing.T) { + aListener, err := FreeTCPPortListener() + if err != nil { + t.Fatal(err) + } + bListener, err := FreeTCPPortListener() + if err != nil { + t.Fatal(err) + } + if aListener.Addr().(*net.TCPAddr).Port == bListener.Addr().(*net.TCPAddr).Port { + t.Errorf("FreeTCPPortListener() got same port: %v, %v", aListener, bListener) + } + if aListener.Addr().(*net.TCPAddr).Port == 0 || bListener.Addr().(*net.TCPAddr).Port == 0 { + t.Errorf("FreeTCPPortListener() got port 0") + } +} diff --git a/util/testhelpers/testhelpers.go b/util/testhelpers/testhelpers.go index bccc26917..071429879 100644 --- a/util/testhelpers/testhelpers.go +++ b/util/testhelpers/testhelpers.go @@ -1,10 +1,14 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package testhelpers import ( - "crypto/rand" + "context" + crypto "crypto/rand" + "io" + "math/big" + "math/rand" "os" "regexp" "sync" @@ -13,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/util/colors" + "golang.org/x/exp/slog" ) // Fail a test should an error occur @@ -29,33 +34,71 @@ func FailImpl(t *testing.T, printables ...interface{}) { } func RandomizeSlice(slice []byte) []byte { - _, err := rand.Read(slice) + _, err := crypto.Read(slice) if err != nil { panic(err) } return slice } +func RandomSlice(size uint64) []byte { + return RandomizeSlice(make([]byte, size)) +} + +func RandomHash() common.Hash { + var hash common.Hash + RandomizeSlice(hash[:]) + return hash +} + func RandomAddress() common.Address { var address common.Address RandomizeSlice(address[:]) return address } +func RandomCallValue(limit int64) *big.Int { + return big.NewInt(rand.Int63n(limit)) +} + +// Computes a psuedo-random uint64 on the interval [min, max] +func RandomUint32(min, max uint32) uint32 { + return uint32(RandomUint64(uint64(min), uint64(max))) +} + +// Computes a psuedo-random uint64 on the interval [min, max] +func RandomUint64(min, max uint64) uint64 { + return uint64(rand.Uint64()%(max-min+1) + min) +} + +func RandomBool() bool { + return rand.Int31n(2) == 0 +} + type LogHandler struct { - mutex sync.Mutex - t *testing.T - records []log.Record - streamHandler log.Handler + mutex sync.Mutex + t *testing.T + records []slog.Record + terminalHandler *log.TerminalHandler +} + +func (h *LogHandler) Enabled(_ context.Context, level slog.Level) bool { + return h.terminalHandler.Enabled(context.Background(), level) +} +func (h *LogHandler) WithGroup(name string) slog.Handler { + return h.terminalHandler.WithGroup(name) +} +func (h *LogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return h.terminalHandler.WithAttrs(attrs) } -func (h *LogHandler) Log(record *log.Record) error { - if err := h.streamHandler.Log(record); err != nil { +func (h *LogHandler) Handle(_ context.Context, record slog.Record) error { + if err := h.terminalHandler.Handle(context.Background(), record); err != nil { return err } h.mutex.Lock() defer h.mutex.Unlock() - h.records = append(h.records, *record) + h.records = append(h.records, record) return nil } @@ -65,7 +108,7 @@ func (h *LogHandler) WasLogged(pattern string) bool { h.mutex.Lock() defer h.mutex.Unlock() for _, record := range h.records { - if re.MatchString(record.Msg) { + if re.MatchString(record.Message) { return true } } @@ -74,16 +117,16 @@ func (h *LogHandler) WasLogged(pattern string) bool { func newLogHandler(t *testing.T) *LogHandler { return &LogHandler{ - t: t, - records: make([]log.Record, 0), - streamHandler: log.StreamHandler(os.Stderr, log.TerminalFormat(false)), + t: t, + records: make([]slog.Record, 0), + terminalHandler: log.NewTerminalHandler(io.Writer(os.Stderr), false), } } -func InitTestLog(t *testing.T, level log.Lvl) *LogHandler { +func InitTestLog(t *testing.T, level slog.Level) *LogHandler { handler := newLogHandler(t) glogger := log.NewGlogHandler(handler) glogger.Verbosity(level) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) return handler } diff --git a/validator/client/redis/producer.go b/validator/client/redis/producer.go new file mode 100644 index 000000000..4aa403135 --- /dev/null +++ b/validator/client/redis/producer.go @@ -0,0 +1,156 @@ +package redis + +import ( + "context" + "fmt" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/go-redis/redis/v8" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api" + "github.com/offchainlabs/nitro/validator/server_common" + "github.com/spf13/pflag" +) + +type ValidationClientConfig struct { + Name string `koanf:"name"` + Room int32 `koanf:"room"` + RedisURL string `koanf:"redis-url"` + ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` + CreateStreams bool `koanf:"create-streams"` +} + +func (c ValidationClientConfig) Enabled() bool { + return c.RedisURL != "" +} + +var DefaultValidationClientConfig = ValidationClientConfig{ + Name: "redis validation client", + Room: 2, + RedisURL: "", + ProducerConfig: pubsub.DefaultProducerConfig, + CreateStreams: true, +} + +var TestValidationClientConfig = ValidationClientConfig{ + Name: "test redis validation client", + Room: 2, + RedisURL: "", + ProducerConfig: pubsub.TestProducerConfig, + CreateStreams: false, +} + +func ValidationClientConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.String(prefix+".name", DefaultValidationClientConfig.Name, "validation client name") + f.Int32(prefix+".room", DefaultValidationClientConfig.Room, "validation client room") + f.String(prefix+".redis-url", DefaultValidationClientConfig.RedisURL, "redis url") + pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) + f.Bool(prefix+".create-streams", DefaultValidationClientConfig.CreateStreams, "create redis streams if it does not exist") +} + +// ValidationClient implements validation client through redis streams. +type ValidationClient struct { + stopwaiter.StopWaiter + name string + room int32 + // producers stores moduleRoot to producer mapping. + producers map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState] + producerConfig pubsub.ProducerConfig + redisClient redis.UniversalClient + moduleRoots []common.Hash + createStreams bool +} + +func NewValidationClient(cfg *ValidationClientConfig) (*ValidationClient, error) { + if cfg.RedisURL == "" { + return nil, fmt.Errorf("redis url cannot be empty") + } + redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) + if err != nil { + return nil, err + } + return &ValidationClient{ + name: cfg.Name, + room: cfg.Room, + producers: make(map[common.Hash]*pubsub.Producer[*validator.ValidationInput, validator.GoGlobalState]), + producerConfig: cfg.ProducerConfig, + redisClient: redisClient, + createStreams: cfg.CreateStreams, + }, nil +} + +func (c *ValidationClient) Initialize(ctx context.Context, moduleRoots []common.Hash) error { + for _, mr := range moduleRoots { + if c.createStreams { + if err := pubsub.CreateStream(ctx, server_api.RedisStreamForRoot(mr), c.redisClient); err != nil { + return fmt.Errorf("creating redis stream: %w", err) + } + } + if _, exists := c.producers[mr]; exists { + log.Warn("Producer already existsw for module root", "hash", mr) + continue + } + p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( + c.redisClient, server_api.RedisStreamForRoot(mr), &c.producerConfig) + if err != nil { + log.Warn("failed init redis for %v: %w", mr, err) + continue + } + p.Start(c.GetContext()) + c.producers[mr] = p + c.moduleRoots = append(c.moduleRoots, mr) + } + return nil +} + +func (c *ValidationClient) WasmModuleRoots() ([]common.Hash, error) { + return c.moduleRoots, nil +} + +func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { + atomic.AddInt32(&c.room, -1) + defer atomic.AddInt32(&c.room, 1) + producer, found := c.producers[moduleRoot] + if !found { + errPromise := containers.NewReadyPromise(validator.GoGlobalState{}, fmt.Errorf("no validation is configured for wasm root %v", moduleRoot)) + return server_common.NewValRun(errPromise, moduleRoot) + } + promise, err := producer.Produce(c.GetContext(), entry) + if err != nil { + errPromise := containers.NewReadyPromise(validator.GoGlobalState{}, fmt.Errorf("error producing input: %w", err)) + return server_common.NewValRun(errPromise, moduleRoot) + } + return server_common.NewValRun(promise, moduleRoot) +} + +func (c *ValidationClient) Start(ctx_in context.Context) error { + for _, p := range c.producers { + p.Start(ctx_in) + } + c.StopWaiter.Start(ctx_in, c) + return nil +} + +func (c *ValidationClient) Stop() { + for _, p := range c.producers { + p.StopAndWait() + } + c.StopWaiter.StopAndWait() +} + +func (c *ValidationClient) Name() string { + if c.Started() { + return c.name + } + return "(not started)" +} + +func (c *ValidationClient) Room() int { + return int(c.room) +} diff --git a/validator/server_api/validation_client.go b/validator/client/validation_client.go similarity index 69% rename from validator/server_api/validation_client.go rename to validator/client/validation_client.go index d6143ca91..fa6b9000f 100644 --- a/validator/server_api/validation_client.go +++ b/validator/client/validation_client.go @@ -1,9 +1,13 @@ -package server_api +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package client import ( "context" "encoding/base64" "errors" + "fmt" "sync/atomic" "time" @@ -13,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/ethereum/go-ethereum/common" @@ -22,9 +27,10 @@ import ( type ValidationClient struct { stopwaiter.StopWaiter - client *rpcclient.RpcClient - name string - room int32 + client *rpcclient.RpcClient + name string + room int32 + wasmModuleRoots []common.Hash } func NewValidationClient(config rpcclient.ClientConfigFetcher, stack *node.Node) *ValidationClient { @@ -36,9 +42,9 @@ func NewValidationClient(config rpcclient.ClientConfigFetcher, stack *node.Node) func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { atomic.AddInt32(&c.room, -1) promise := stopwaiter.LaunchPromiseThread[validator.GoGlobalState](c, func(ctx context.Context) (validator.GoGlobalState, error) { - input := ValidationInputToJson(entry) + input := server_api.ValidationInputToJson(entry) var res validator.GoGlobalState - err := c.client.CallContext(ctx, &res, Namespace+"_validate", input, moduleRoot) + err := c.client.CallContext(ctx, &res, server_api.Namespace+"_validate", input, moduleRoot) atomic.AddInt32(&c.room, 1) return res, err }) @@ -48,21 +54,25 @@ func (c *ValidationClient) Launch(entry *validator.ValidationInput, moduleRoot c func (c *ValidationClient) Start(ctx_in context.Context) error { c.StopWaiter.Start(ctx_in, c) ctx := c.GetContext() - err := c.client.Start(ctx) - if err != nil { + if err := c.client.Start(ctx); err != nil { return err } var name string - err = c.client.CallContext(ctx, &name, Namespace+"_name") - if err != nil { + if err := c.client.CallContext(ctx, &name, server_api.Namespace+"_name"); err != nil { return err } if len(name) == 0 { return errors.New("couldn't read name from server") } + var moduleRoots []common.Hash + if err := c.client.CallContext(c.GetContext(), &moduleRoots, server_api.Namespace+"_wasmModuleRoots"); err != nil { + return err + } + if len(moduleRoots) == 0 { + return fmt.Errorf("server reported no wasmModuleRoots") + } var room int - err = c.client.CallContext(c.GetContext(), &room, Namespace+"_room") - if err != nil { + if err := c.client.CallContext(c.GetContext(), &room, server_api.Namespace+"_room"); err != nil { return err } if room < 2 { @@ -72,10 +82,18 @@ func (c *ValidationClient) Start(ctx_in context.Context) error { log.Info("connected to validation server", "name", name, "room", room) } atomic.StoreInt32(&c.room, int32(room)) + c.wasmModuleRoots = moduleRoots c.name = name return nil } +func (c *ValidationClient) WasmModuleRoots() ([]common.Hash, error) { + if c.Started() { + return c.wasmModuleRoots, nil + } + return nil, errors.New("not started") +} + func (c *ValidationClient) Stop() { c.StopWaiter.StopOnly() if c.client != nil { @@ -84,10 +102,7 @@ func (c *ValidationClient) Stop() { } func (c *ValidationClient) Name() string { - if c.Started() { - return c.name - } - return "(not started)" + return c.name } func (c *ValidationClient) Room() int { @@ -111,7 +126,7 @@ func NewExecutionClient(config rpcclient.ClientConfigFetcher, stack *node.Node) func (c *ExecutionClient) CreateExecutionRun(wasmModuleRoot common.Hash, input *validator.ValidationInput) containers.PromiseInterface[validator.ExecutionRun] { return stopwaiter.LaunchPromiseThread[validator.ExecutionRun](c, func(ctx context.Context) (validator.ExecutionRun, error) { var res uint64 - err := c.client.CallContext(ctx, &res, Namespace+"_createExecutionRun", wasmModuleRoot, ValidationInputToJson(input)) + err := c.client.CallContext(ctx, &res, server_api.Namespace+"_createExecutionRun", wasmModuleRoot, server_api.ValidationInputToJson(input)) if err != nil { return nil, err } @@ -133,7 +148,7 @@ type ExecutionClientRun struct { func (c *ExecutionClient) LatestWasmModuleRoot() containers.PromiseInterface[common.Hash] { return stopwaiter.LaunchPromiseThread[common.Hash](c, func(ctx context.Context) (common.Hash, error) { var res common.Hash - err := c.client.CallContext(ctx, &res, Namespace+"_latestWasmModuleRoot") + err := c.client.CallContext(ctx, &res, server_api.Namespace+"_latestWasmModuleRoot") if err != nil { return common.Hash{}, err } @@ -142,15 +157,20 @@ func (c *ExecutionClient) LatestWasmModuleRoot() containers.PromiseInterface[com } func (c *ExecutionClient) WriteToFile(input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] { - jsonInput := ValidationInputToJson(input) + jsonInput := server_api.ValidationInputToJson(input) + if err := jsonInput.WriteToFile(); err != nil { + return stopwaiter.LaunchPromiseThread[struct{}](c, func(ctx context.Context) (struct{}, error) { + return struct{}{}, err + }) + } return stopwaiter.LaunchPromiseThread[struct{}](c, func(ctx context.Context) (struct{}, error) { - err := c.client.CallContext(ctx, nil, Namespace+"_writeToFile", jsonInput, expOut, moduleRoot) + err := c.client.CallContext(ctx, nil, server_api.Namespace+"_writeToFile", jsonInput, expOut, moduleRoot) return struct{}{}, err }) } func (r *ExecutionClientRun) SendKeepAlive(ctx context.Context) time.Duration { - err := r.client.client.CallContext(ctx, nil, Namespace+"_execKeepAlive", r.id) + err := r.client.client.CallContext(ctx, nil, server_api.Namespace+"_execKeepAlive", r.id) if err != nil { log.Error("execution run keepalive failed", "err", err) } @@ -164,12 +184,12 @@ func (r *ExecutionClientRun) Start(ctx_in context.Context) { func (r *ExecutionClientRun) GetStepAt(pos uint64) containers.PromiseInterface[*validator.MachineStepResult] { return stopwaiter.LaunchPromiseThread[*validator.MachineStepResult](r, func(ctx context.Context) (*validator.MachineStepResult, error) { - var resJson MachineStepResultJson - err := r.client.client.CallContext(ctx, &resJson, Namespace+"_getStepAt", r.id, pos) + var resJson server_api.MachineStepResultJson + err := r.client.client.CallContext(ctx, &resJson, server_api.Namespace+"_getStepAt", r.id, pos) if err != nil { return nil, err } - res, err := MachineStepResultFromJson(&resJson) + res, err := server_api.MachineStepResultFromJson(&resJson) if err != nil { return nil, err } @@ -180,7 +200,7 @@ func (r *ExecutionClientRun) GetStepAt(pos uint64) containers.PromiseInterface[* func (r *ExecutionClientRun) GetProofAt(pos uint64) containers.PromiseInterface[[]byte] { return stopwaiter.LaunchPromiseThread[[]byte](r, func(ctx context.Context) ([]byte, error) { var resString string - err := r.client.client.CallContext(ctx, &resString, Namespace+"_getProofAt", r.id, pos) + err := r.client.client.CallContext(ctx, &resString, server_api.Namespace+"_getProofAt", r.id, pos) if err != nil { return nil, err } @@ -194,7 +214,7 @@ func (r *ExecutionClientRun) GetLastStep() containers.PromiseInterface[*validato func (r *ExecutionClientRun) PrepareRange(start, end uint64) containers.PromiseInterface[struct{}] { return stopwaiter.LaunchPromiseThread[struct{}](r, func(ctx context.Context) (struct{}, error) { - err := r.client.client.CallContext(ctx, nil, Namespace+"_prepareRange", r.id, start, end) + err := r.client.client.CallContext(ctx, nil, server_api.Namespace+"_prepareRange", r.id, start, end) if err != nil && ctx.Err() == nil { log.Warn("prepare execution got error", "err", err) } @@ -205,7 +225,7 @@ func (r *ExecutionClientRun) PrepareRange(start, end uint64) containers.PromiseI func (r *ExecutionClientRun) Close() { r.StopOnly() r.LaunchUntrackedThread(func() { - err := r.client.client.CallContext(r.GetParentContext(), nil, Namespace+"_closeExec", r.id) + err := r.client.client.CallContext(r.GetParentContext(), nil, server_api.Namespace+"_closeExec", r.id) if err != nil { log.Warn("closing execution client run got error", "err", err, "client", r.client.Name(), "id", r.id) } diff --git a/validator/interface.go b/validator/interface.go index 5785ac4de..0324b996e 100644 --- a/validator/interface.go +++ b/validator/interface.go @@ -9,6 +9,7 @@ import ( type ValidationSpawner interface { Launch(entry *ValidationInput, moduleRoot common.Hash) ValidationRun + WasmModuleRoots() ([]common.Hash, error) Start(context.Context) error Stop() Name() string diff --git a/validator/server_api/json.go b/validator/server_api/json.go index 202974198..3dd817d5a 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -5,19 +5,56 @@ package server_api import ( "encoding/base64" + "encoding/json" + "fmt" + "os" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/validator" ) -type BatchInfoJson struct { - Number uint64 - DataB64 string +const Namespace string = "validation" + +type MachineStepResultJson struct { + Hash common.Hash + Position uint64 + Status uint8 + GlobalState validator.GoGlobalState } -type ValidationInputJson struct { +func MachineStepResultToJson(result *validator.MachineStepResult) *MachineStepResultJson { + return &MachineStepResultJson{ + Hash: result.Hash, + Position: result.Position, + Status: uint8(result.Status), + GlobalState: result.GlobalState, + } +} + +func MachineStepResultFromJson(resultJson *MachineStepResultJson) (*validator.MachineStepResult, error) { + + return &validator.MachineStepResult{ + Hash: resultJson.Hash, + Position: resultJson.Position, + Status: validator.MachineStatus(resultJson.Status), + GlobalState: resultJson.GlobalState, + }, nil +} + +func RedisStreamForRoot(moduleRoot common.Hash) string { + return fmt.Sprintf("stream:%s", moduleRoot.Hex()) +} + +type Request struct { + Input *InputJSON + ModuleRoot common.Hash +} + +type InputJSON struct { Id uint64 HasDelayedMsg bool DelayedMsgNr uint64 @@ -25,29 +62,61 @@ type ValidationInputJson struct { BatchInfo []BatchInfoJson DelayedMsgB64 string StartState validator.GoGlobalState + UserWasms map[common.Hash]UserWasmJson + DebugChain bool +} + +func (i *InputJSON) WriteToFile() error { + contents, err := json.MarshalIndent(i, "", " ") + if err != nil { + return err + } + if err = os.WriteFile(fmt.Sprintf("block_inputs_%d.json", i.Id), contents, 0600); err != nil { + return err + } + return nil } -func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJson { +type UserWasmJson struct { + Module string + Asm string +} + +type BatchInfoJson struct { + Number uint64 + DataB64 string +} + +func ValidationInputToJson(entry *validator.ValidationInput) *InputJSON { jsonPreimagesMap := make(map[arbutil.PreimageType]*jsonapi.PreimagesMapJson) for ty, preimages := range entry.Preimages { jsonPreimagesMap[ty] = jsonapi.NewPreimagesMapJson(preimages) } - res := &ValidationInputJson{ + res := &InputJSON{ Id: entry.Id, HasDelayedMsg: entry.HasDelayedMsg, DelayedMsgNr: entry.DelayedMsgNr, DelayedMsgB64: base64.StdEncoding.EncodeToString(entry.DelayedMsg), StartState: entry.StartState, PreimagesB64: jsonPreimagesMap, + UserWasms: make(map[common.Hash]UserWasmJson), + DebugChain: entry.DebugChain, } for _, binfo := range entry.BatchInfo { encData := base64.StdEncoding.EncodeToString(binfo.Data) - res.BatchInfo = append(res.BatchInfo, BatchInfoJson{binfo.Number, encData}) + res.BatchInfo = append(res.BatchInfo, BatchInfoJson{Number: binfo.Number, DataB64: encData}) + } + for moduleHash, info := range entry.UserWasms { + encWasm := UserWasmJson{ + Asm: base64.StdEncoding.EncodeToString(info.Asm), + Module: base64.StdEncoding.EncodeToString(info.Module), + } + res.UserWasms[moduleHash] = encWasm } return res } -func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationInput, error) { +func ValidationInputFromJson(entry *InputJSON) (*validator.ValidationInput, error) { preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) for ty, jsonPreimages := range entry.PreimagesB64 { preimages[ty] = jsonPreimages.Map @@ -58,6 +127,8 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI DelayedMsgNr: entry.DelayedMsgNr, StartState: entry.StartState, Preimages: preimages, + UserWasms: make(state.UserWasms), + DebugChain: entry.DebugChain, } delayed, err := base64.StdEncoding.DecodeString(entry.DelayedMsgB64) if err != nil { @@ -75,31 +146,20 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI } valInput.BatchInfo = append(valInput.BatchInfo, decInfo) } - return valInput, nil -} - -type MachineStepResultJson struct { - Hash common.Hash - Position uint64 - Status uint8 - GlobalState validator.GoGlobalState -} - -func MachineStepResultToJson(result *validator.MachineStepResult) *MachineStepResultJson { - return &MachineStepResultJson{ - Hash: result.Hash, - Position: result.Position, - Status: uint8(result.Status), - GlobalState: result.GlobalState, + for moduleHash, info := range entry.UserWasms { + asm, err := base64.StdEncoding.DecodeString(info.Asm) + if err != nil { + return nil, err + } + module, err := base64.StdEncoding.DecodeString(info.Module) + if err != nil { + return nil, err + } + decInfo := state.ActivatedWasm{ + Asm: asm, + Module: module, + } + valInput.UserWasms[moduleHash] = decInfo } -} - -func MachineStepResultFromJson(resultJson *MachineStepResultJson) (*validator.MachineStepResult, error) { - - return &validator.MachineStepResult{ - Hash: resultJson.Hash, - Position: resultJson.Position, - Status: validator.MachineStatus(resultJson.Status), - GlobalState: resultJson.GlobalState, - }, nil + return valInput, nil } diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index e59659c7a..cffd3db0e 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE package server_arb @@ -22,10 +22,17 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/validator" ) +type u8 = C.uint8_t +type u16 = C.uint16_t +type u32 = C.uint32_t +type u64 = C.uint64_t +type usize = C.size_t + type MachineInterface interface { CloneMachineInterface() MachineInterface GetStepCount() uint64 @@ -81,10 +88,11 @@ func machineFromPointer(ptr *C.struct_Machine) *ArbitratorMachine { return mach } -func LoadSimpleMachine(wasm string, libraries []string) (*ArbitratorMachine, error) { +func LoadSimpleMachine(wasm string, libraries []string, debugChain bool) (*ArbitratorMachine, error) { cWasm := C.CString(wasm) cLibraries := CreateCStringList(libraries) - mach := C.arbitrator_load_machine(cWasm, cLibraries, C.long(len(libraries))) + debug := usize(arbmath.BoolToUint32(debugChain)) + mach := C.arbitrator_load_machine(cWasm, cLibraries, C.long(len(libraries)), debug) C.free(unsafe.Pointer(cWasm)) FreeCStringList(cLibraries, len(libraries)) if mach == nil { @@ -171,8 +179,8 @@ func (m *ArbitratorMachine) ValidForStep(requestedStep uint64) bool { } } -func manageConditionByte(ctx context.Context) (*C.uint8_t, func()) { - var zero C.uint8_t +func manageConditionByte(ctx context.Context) (*u8, func()) { + var zero u8 conditionByte := &zero doneEarlyChan := make(chan struct{}) @@ -205,11 +213,10 @@ func (m *ArbitratorMachine) Step(ctx context.Context, count uint64) error { conditionByte, cancel := manageConditionByte(ctx) defer cancel() - err := C.arbitrator_step(m.ptr, C.uint64_t(count), conditionByte) + err := C.arbitrator_step(m.ptr, u64(count), conditionByte) + defer C.free(unsafe.Pointer(err)) if err != nil { - errString := C.GoString(err) - C.free(unsafe.Pointer(err)) - return errors.New(errString) + return errors.New(C.GoString(err)) } return ctx.Err() @@ -226,7 +233,11 @@ func (m *ArbitratorMachine) StepUntilHostIo(ctx context.Context) error { conditionByte, cancel := manageConditionByte(ctx) defer cancel() - C.arbitrator_step_until_host_io(m.ptr, conditionByte) + err := C.arbitrator_step_until_host_io(m.ptr, conditionByte) + defer C.free(unsafe.Pointer(err)) + if err != nil { + return errors.New(C.GoString(err)) + } return ctx.Err() } @@ -252,6 +263,7 @@ func (m *ArbitratorMachine) GetModuleRoot() (hash common.Hash) { } return } + func (m *ArbitratorMachine) ProveNextStep() []byte { defer runtime.KeepAlive(m) m.mutex.Lock() @@ -309,7 +321,7 @@ func (m *ArbitratorMachine) AddSequencerInboxMessage(index uint64, data []byte) return errors.New("machine frozen") } cbyte := CreateCByteArray(data) - status := C.arbitrator_add_inbox_message(m.ptr, C.uint64_t(0), C.uint64_t(index), cbyte) + status := C.arbitrator_add_inbox_message(m.ptr, u64(0), u64(index), cbyte) DestroyCByteArray(cbyte) if status != 0 { return errors.New("failed to add sequencer inbox message") @@ -328,7 +340,7 @@ func (m *ArbitratorMachine) AddDelayedInboxMessage(index uint64, data []byte) er } cbyte := CreateCByteArray(data) - status := C.arbitrator_add_inbox_message(m.ptr, C.uint64_t(1), C.uint64_t(index), cbyte) + status := C.arbitrator_add_inbox_message(m.ptr, u64(1), u64(index), cbyte) DestroyCByteArray(cbyte) if status != 0 { return errors.New("failed to add sequencer inbox message") @@ -358,7 +370,7 @@ func preimageResolver(context C.size_t, ty C.uint8_t, ptr unsafe.Pointer) C.Reso } } return C.ResolvedPreimage{ - ptr: (*C.uint8_t)(C.CBytes(preimage)), + ptr: (*u8)(C.CBytes(preimage)), len: (C.ptrdiff_t)(len(preimage)), } } @@ -374,6 +386,24 @@ func (m *ArbitratorMachine) SetPreimageResolver(resolver GoPreimageResolver) err preimageResolvers.Store(id, resolver) m.contextId = &id runtime.SetFinalizer(m.contextId, freeContextId) - C.arbitrator_set_context(m.ptr, C.uint64_t(id)) + C.arbitrator_set_context(m.ptr, u64(id)) + return nil +} + +func (m *ArbitratorMachine) AddUserWasm(moduleHash common.Hash, module []byte) error { + defer runtime.KeepAlive(m) + if m.frozen { + return errors.New("machine frozen") + } + hashBytes := [32]u8{} + for index, byte := range moduleHash.Bytes() { + hashBytes[index] = u8(byte) + } + C.arbitrator_add_user_wasm( + m.ptr, + (*u8)(arbutil.SliceToPointer(module)), + usize(len(module)), + &C.struct_Bytes32{hashBytes}, + ) return nil } diff --git a/validator/server_arb/nitro_machine.go b/validator/server_arb/nitro_machine.go index acaf3b10e..2b2cb230b 100644 --- a/validator/server_arb/nitro_machine.go +++ b/validator/server_arb/nitro_machine.go @@ -1,5 +1,5 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// Copyright 2021-2023, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE package server_arb diff --git a/validator/server_arb/prover_interface.go b/validator/server_arb/prover_interface.go index 0cc1d0be8..bdd81ed58 100644 --- a/validator/server_arb/prover_interface.go +++ b/validator/server_arb/prover_interface.go @@ -1,11 +1,11 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package server_arb /* -#cgo CFLAGS: -g -Wall -I../../target/include/ -#cgo LDFLAGS: ${SRCDIR}/../../target/lib/libprover.a -ldl -lm +#cgo CFLAGS: -g -Wall -I../target/include/ +#cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm #include "arbitrator.h" #include diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index 67aa5477e..dca15c369 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -11,13 +11,14 @@ import ( "sync/atomic" "time" - flag "github.com/spf13/pflag" + "github.com/spf13/pflag" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_common" + "github.com/offchainlabs/nitro/validator/valnode/redis" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -27,26 +28,29 @@ import ( var arbitratorValidationSteps = metrics.NewRegisteredHistogram("arbitrator/validation/steps", nil, metrics.NewBoundedHistogramSample()) type ArbitratorSpawnerConfig struct { - Workers int `koanf:"workers" reload:"hot"` - OutputPath string `koanf:"output-path" reload:"hot"` - Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only - ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` + Workers int `koanf:"workers" reload:"hot"` + OutputPath string `koanf:"output-path" reload:"hot"` + Execution MachineCacheConfig `koanf:"execution" reload:"hot"` // hot reloading for new executions only + ExecutionRunTimeout time.Duration `koanf:"execution-run-timeout" reload:"hot"` + RedisValidationServerConfig redis.ValidationServerConfig `koanf:"redis-validation-server-config"` } type ArbitratorSpawnerConfigFecher func() *ArbitratorSpawnerConfig var DefaultArbitratorSpawnerConfig = ArbitratorSpawnerConfig{ - Workers: 0, - OutputPath: "./target/output", - Execution: DefaultMachineCacheConfig, - ExecutionRunTimeout: time.Minute * 15, + Workers: 0, + OutputPath: "./target/output", + Execution: DefaultMachineCacheConfig, + ExecutionRunTimeout: time.Minute * 15, + RedisValidationServerConfig: redis.DefaultValidationServerConfig, } -func ArbitratorSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { +func ArbitratorSpawnerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Int(prefix+".workers", DefaultArbitratorSpawnerConfig.Workers, "number of concurrent validation threads") f.Duration(prefix+".execution-run-timeout", DefaultArbitratorSpawnerConfig.ExecutionRunTimeout, "timeout before discarding execution run") f.String(prefix+".output-path", DefaultArbitratorSpawnerConfig.OutputPath, "path to write machines to") MachineCacheConfigConfigAddOptions(prefix+".execution", f) + redis.ValidationServerConfigAddOptions(prefix+".redis-validation-server-config", f) } func DefaultArbitratorSpawnerConfigFetcher() *ArbitratorSpawnerConfig { @@ -80,6 +84,10 @@ func (s *ArbitratorSpawner) LatestWasmModuleRoot() containers.PromiseInterface[c return containers.NewReadyPromise(s.locator.LatestWasmModuleRoot(), nil) } +func (s *ArbitratorSpawner) WasmModuleRoots() ([]common.Hash, error) { + return s.locator.ModuleRoots(), nil +} + func (s *ArbitratorSpawner) Name() string { return "arbitrator" } @@ -110,6 +118,16 @@ func (v *ArbitratorSpawner) loadEntryToMachine(ctx context.Context, entry *valid return fmt.Errorf("error while trying to add sequencer msg for proving: %w", err) } } + for moduleHash, info := range entry.UserWasms { + err = mach.AddUserWasm(moduleHash, info.Module) + if err != nil { + log.Error( + "error adding user wasm for proving", + "err", err, "moduleHash", moduleHash, "blockNr", entry.Id, + ) + return fmt.Errorf("error adding user wasm for proving: %w", err) + } + } if entry.HasDelayedMsg { err = mach.AddDelayedInboxMessage(entry.DelayedMsgNr, entry.DelayedMsg) if err != nil { diff --git a/validator/server_common/machine_locator.go b/validator/server_common/machine_locator.go index 4c25448dd..71f6af60b 100644 --- a/validator/server_common/machine_locator.go +++ b/validator/server_common/machine_locator.go @@ -8,21 +8,20 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" ) type MachineLocator struct { - rootPath string - latest common.Hash + rootPath string + latest common.Hash + moduleRoots []common.Hash } var ErrMachineNotFound = errors.New("machine not found") func NewMachineLocator(rootPath string) (*MachineLocator, error) { - var places []string - - if rootPath != "" { - places = append(places, rootPath) - } else { + dirs := []string{rootPath} + if rootPath == "" { // Check the project dir: /arbnode/node.go => ../../target/machines _, thisFile, _, ok := runtime.Caller(0) if !ok { @@ -30,7 +29,7 @@ func NewMachineLocator(rootPath string) (*MachineLocator, error) { } projectDir := filepath.Dir(filepath.Dir(filepath.Dir(thisFile))) projectPath := filepath.Join(filepath.Join(projectDir, "target"), "machines") - places = append(places, projectPath) + dirs = append(dirs, projectPath) // Check the working directory: ./machines and ./target/machines workDir, err := os.Getwd() @@ -39,8 +38,8 @@ func NewMachineLocator(rootPath string) (*MachineLocator, error) { } workPath1 := filepath.Join(workDir, "machines") workPath2 := filepath.Join(filepath.Join(workDir, "target"), "machines") - places = append(places, workPath1) - places = append(places, workPath2) + dirs = append(dirs, workPath1) + dirs = append(dirs, workPath2) // Check above the executable: => ../../machines execfile, err := os.Executable() @@ -48,22 +47,62 @@ func NewMachineLocator(rootPath string) (*MachineLocator, error) { return nil, err } execPath := filepath.Join(filepath.Dir(filepath.Dir(execfile)), "machines") - places = append(places, execPath) + dirs = append(dirs, execPath) } - for _, place := range places { - if _, err := os.Stat(place); err == nil { - var latestModuleRoot common.Hash - latestModuleRootPath := filepath.Join(place, "latest", "module-root.txt") - fileBytes, err := os.ReadFile(latestModuleRootPath) - if err == nil { - s := strings.TrimSpace(string(fileBytes)) - latestModuleRoot = common.HexToHash(s) + var ( + moduleRoots = make(map[common.Hash]bool) + latestModuleRoot common.Hash + ) + + for _, dir := range dirs { + fInfo, err := os.Stat(dir) + if err != nil { + log.Warn("Getting file info", "dir", dir, "error", err) + continue + } + if !fInfo.IsDir() { + // Skip files that are not directories. + continue + } + files, err := os.ReadDir(dir) + if err != nil { + log.Warn("Reading directory", "dir", dir, "error", err) + } + for _, file := range files { + mrFile := filepath.Join(dir, file.Name(), "module-root.txt") + if _, err := os.Stat(mrFile); err != nil { + // Skip if module-roots file does not exist. + continue } - return &MachineLocator{place, latestModuleRoot}, nil + mrContent, err := os.ReadFile(mrFile) + if err != nil { + log.Warn("Reading module roots file", "file path", mrFile, "error", err) + continue + } + moduleRoot := common.HexToHash(strings.TrimSpace(string(mrContent))) + if file.Name() != "latest" && file.Name() != moduleRoot.Hex() { + continue + } + moduleRoots[moduleRoot] = true + if file.Name() == "latest" { + latestModuleRoot = moduleRoot + } + rootPath = dir + } + if rootPath != "" { + break } } - return nil, ErrMachineNotFound + var roots []common.Hash + for k := range moduleRoots { + roots = append(roots, k) + } + return &MachineLocator{ + rootPath: rootPath, + latest: latestModuleRoot, + moduleRoots: roots, + }, nil } func (l MachineLocator) GetMachinePath(moduleRoot common.Hash) string { @@ -81,3 +120,7 @@ func (l MachineLocator) LatestWasmModuleRoot() common.Hash { func (l MachineLocator) RootPath() string { return l.rootPath } + +func (l MachineLocator) ModuleRoots() []common.Hash { + return l.moduleRoots +} diff --git a/validator/server_common/machine_locator_test.go b/validator/server_common/machine_locator_test.go new file mode 100644 index 000000000..ac664fe66 --- /dev/null +++ b/validator/server_common/machine_locator_test.go @@ -0,0 +1,39 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package server_common + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" +) + +var ( + wantLatestModuleRoot = "0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a" + wantModuleRoots = []string{ + "0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4", + "0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4", + "0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a", + } +) + +func TestNewMachineLocator(t *testing.T) { + ml, err := NewMachineLocator("testdata") + if err != nil { + t.Fatalf("Error creating new machine locator: %v", err) + } + if ml.latest.Hex() != wantLatestModuleRoot { + t.Errorf("NewMachineLocator() got latestModuleRoot: %v, want: %v", ml.latest, wantLatestModuleRoot) + } + var got []string + for _, s := range ml.ModuleRoots() { + got = append(got, s.Hex()) + } + sort.Strings(got) + sort.Strings(wantModuleRoots) + if diff := cmp.Diff(got, wantModuleRoots); diff != "" { + t.Errorf("NewMachineLocator() unexpected diff (-want +got):\n%s", diff) + } +} diff --git a/validator/server_common/testdata/0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4/module-root.txt b/validator/server_common/testdata/0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4/module-root.txt new file mode 100644 index 000000000..067f2db9f --- /dev/null +++ b/validator/server_common/testdata/0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4/module-root.txt @@ -0,0 +1 @@ +0x68e4fe5023f792d4ef584796c84d710303a5e12ea02d6e37e2b5e9c4332507c4 diff --git a/validator/server_common/testdata/0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4/module-root.txt b/validator/server_common/testdata/0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4/module-root.txt new file mode 100644 index 000000000..ad3a905ab --- /dev/null +++ b/validator/server_common/testdata/0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4/module-root.txt @@ -0,0 +1 @@ +0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4 diff --git a/validator/server_common/testdata/0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a/module-root.txt b/validator/server_common/testdata/0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a/module-root.txt new file mode 100644 index 000000000..1a359ae1c --- /dev/null +++ b/validator/server_common/testdata/0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a/module-root.txt @@ -0,0 +1 @@ +0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a diff --git a/validator/server_common/testdata/latest b/validator/server_common/testdata/latest new file mode 120000 index 000000000..42d98792a --- /dev/null +++ b/validator/server_common/testdata/latest @@ -0,0 +1 @@ +0xf4389b835497a910d7ba3ebfb77aa93da985634f3c052de1290360635be40c4a \ No newline at end of file diff --git a/validator/server_jit/jit_machine.go b/validator/server_jit/jit_machine.go index a41e249cd..1a3ccfa34 100644 --- a/validator/server_jit/jit_machine.go +++ b/validator/server_jit/jit_machine.go @@ -98,7 +98,7 @@ func (machine *JitMachine) prove( // Wait for the forked process to connect conn, err := tcp.Accept() if err != nil { - return state, err + return state, fmt.Errorf("error waiting for jit machine to connect back to validator: %w", err) } go func() { <-ctx.Done() @@ -121,6 +121,9 @@ func (machine *JitMachine) prove( writeUint8 := func(data uint8) error { return writeExact([]byte{data}) } + writeUint32 := func(data uint32) error { + return writeExact(arbmath.Uint32ToBytes(data)) + } writeUint64 := func(data uint64) error { return writeExact(arbmath.UintToBytes(data)) } @@ -188,14 +191,14 @@ func (machine *JitMachine) prove( // send known preimages preimageTypes := entry.Preimages - if err := writeUint64(uint64(len(preimageTypes))); err != nil { + if err := writeUint32(uint32(len(preimageTypes))); err != nil { return state, err } for ty, preimages := range preimageTypes { if err := writeUint8(uint8(ty)); err != nil { return state, err } - if err := writeUint64(uint64(len(preimages))); err != nil { + if err := writeUint32(uint32(len(preimages))); err != nil { return state, err } for hash, preimage := range preimages { @@ -208,6 +211,20 @@ func (machine *JitMachine) prove( } } + // send user wasms + userWasms := entry.UserWasms + if err := writeUint32(uint32(len(userWasms))); err != nil { + return state, err + } + for moduleHash, info := range userWasms { + if err := writeExact(moduleHash[:]); err != nil { + return state, err + } + if err := writeBytes(info.Asm); err != nil { + return state, err + } + } + // signal that we are done sending global state if err := writeExact(ready); err != nil { return state, err diff --git a/validator/server_jit/machine_loader.go b/validator/server_jit/machine_loader.go index 3a831928b..b2bdb6532 100644 --- a/validator/server_jit/machine_loader.go +++ b/validator/server_jit/machine_loader.go @@ -27,13 +27,16 @@ var DefaultJitMachineConfig = JitMachineConfig{ func getJitPath() (string, error) { var jitBinary string executable, err := os.Executable() + println("executable: ", executable) if err == nil { - if strings.Contains(filepath.Base(executable), "test") { + if strings.Contains(filepath.Base(executable), "test") || strings.Contains(filepath.Dir(executable), "system_tests") { _, thisfile, _, _ := runtime.Caller(0) projectDir := filepath.Dir(filepath.Dir(filepath.Dir(thisfile))) + println("projectDir: ", projectDir) jitBinary = filepath.Join(projectDir, "target", "bin", "jit") } else { jitBinary = filepath.Join(filepath.Dir(executable), "jit") + println("inside else: ", jitBinary) } _, err = os.Stat(jitBinary) } diff --git a/validator/server_jit/spawner.go b/validator/server_jit/spawner.go index 6489821b5..703e761af 100644 --- a/validator/server_jit/spawner.go +++ b/validator/server_jit/spawner.go @@ -67,6 +67,10 @@ func (v *JitSpawner) Start(ctx_in context.Context) error { return nil } +func (v *JitSpawner) WasmModuleRoots() ([]common.Hash, error) { + return v.locator.ModuleRoots(), nil +} + func (v *JitSpawner) execute( ctx context.Context, entry *validator.ValidationInput, moduleRoot common.Hash, ) (validator.GoGlobalState, error) { diff --git a/validator/utils.go b/validator/utils.go new file mode 100644 index 000000000..4c8ae65d0 --- /dev/null +++ b/validator/utils.go @@ -0,0 +1,20 @@ +package validator + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +func SpawnerSupportsModule(spawner ValidationSpawner, requested common.Hash) bool { + supported, err := spawner.WasmModuleRoots() + if err != nil { + log.Warn("WasmModuleRoots returned error", "err", err) + return false + } + for _, root := range supported { + if root == requested { + return true + } + } + return false +} diff --git a/validator/validation_entry.go b/validator/validation_entry.go index 8bb021335..446f84ca6 100644 --- a/validator/validation_entry.go +++ b/validator/validation_entry.go @@ -2,6 +2,7 @@ package validator import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/offchainlabs/nitro/arbutil" ) @@ -16,7 +17,9 @@ type ValidationInput struct { HasDelayedMsg bool DelayedMsgNr uint64 Preimages map[arbutil.PreimageType]map[common.Hash][]byte + UserWasms state.UserWasms BatchInfo []BatchInfo DelayedMsg []byte StartState GoGlobalState + DebugChain bool } diff --git a/validator/valnode/redis/consumer.go b/validator/valnode/redis/consumer.go new file mode 100644 index 000000000..016f30bd6 --- /dev/null +++ b/validator/valnode/redis/consumer.go @@ -0,0 +1,157 @@ +package redis + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api" + "github.com/spf13/pflag" +) + +// ValidationServer implements consumer for the requests originated from +// RedisValidationClient producers. +type ValidationServer struct { + stopwaiter.StopWaiter + spawner validator.ValidationSpawner + + // consumers stores moduleRoot to consumer mapping. + consumers map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState] + streamTimeout time.Duration +} + +func NewValidationServer(cfg *ValidationServerConfig, spawner validator.ValidationSpawner) (*ValidationServer, error) { + if cfg.RedisURL == "" { + return nil, fmt.Errorf("redis url cannot be empty") + } + redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) + if err != nil { + return nil, err + } + consumers := make(map[common.Hash]*pubsub.Consumer[*validator.ValidationInput, validator.GoGlobalState]) + for _, hash := range cfg.ModuleRoots { + mr := common.HexToHash(hash) + c, err := pubsub.NewConsumer[*validator.ValidationInput, validator.GoGlobalState](redisClient, server_api.RedisStreamForRoot(mr), &cfg.ConsumerConfig) + if err != nil { + return nil, fmt.Errorf("creating consumer for validation: %w", err) + } + consumers[mr] = c + } + return &ValidationServer{ + consumers: consumers, + spawner: spawner, + streamTimeout: cfg.StreamTimeout, + }, nil +} + +func (s *ValidationServer) Start(ctx_in context.Context) { + s.StopWaiter.Start(ctx_in, s) + // Channel that all consumers use to indicate their readiness. + readyStreams := make(chan struct{}, len(s.consumers)) + for moduleRoot, c := range s.consumers { + c := c + moduleRoot := moduleRoot + c.Start(ctx_in) + // Channel for single consumer, once readiness is indicated in this, + // consumer will start consuming iteratively. + ready := make(chan struct{}, 1) + s.StopWaiter.LaunchThread(func(ctx context.Context) { + for { + if pubsub.StreamExists(ctx, c.StreamName(), c.RedisClient()) { + ready <- struct{}{} + readyStreams <- struct{}{} + return + } + select { + case <-ctx.Done(): + log.Info("Context done while checking redis stream existance", "error", ctx.Err().Error()) + return + case <-time.After(time.Millisecond * 100): + } + } + }) + s.StopWaiter.LaunchThread(func(ctx context.Context) { + select { + case <-ctx.Done(): + log.Info("Context done while waiting a redis stream to be ready", "error", ctx.Err().Error()) + return + case <-ready: // Wait until the stream exists and start consuming iteratively. + } + s.StopWaiter.CallIteratively(func(ctx context.Context) time.Duration { + req, err := c.Consume(ctx) + if err != nil { + log.Error("Consuming request", "error", err) + return 0 + } + if req == nil { + // There's nothing in the queue. + return time.Second + } + valRun := s.spawner.Launch(req.Value, moduleRoot) + res, err := valRun.Await(ctx) + if err != nil { + log.Error("Error validating", "request value", req.Value, "error", err) + return 0 + } + if err := c.SetResult(ctx, req.ID, res); err != nil { + log.Error("Error setting result for request", "id", req.ID, "result", res, "error", err) + return 0 + } + return time.Second + }) + }) + } + s.StopWaiter.LaunchThread(func(ctx context.Context) { + for { + select { + case <-readyStreams: + log.Trace("At least one stream is ready") + return // Don't block Start if at least one of the stream is ready. + case <-time.After(s.streamTimeout): + log.Error("Waiting for redis streams timed out") + case <-ctx.Done(): + log.Info("Context done while waiting redis streams to be ready, failed to start") + return + } + } + }) +} + +type ValidationServerConfig struct { + RedisURL string `koanf:"redis-url"` + ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + // Supported wasm module roots. + ModuleRoots []string `koanf:"module-roots"` + // Timeout on polling for existence of each redis stream. + StreamTimeout time.Duration `koanf:"stream-timeout"` +} + +var DefaultValidationServerConfig = ValidationServerConfig{ + RedisURL: "", + ConsumerConfig: pubsub.DefaultConsumerConfig, + ModuleRoots: []string{}, + StreamTimeout: 10 * time.Minute, +} + +var TestValidationServerConfig = ValidationServerConfig{ + RedisURL: "", + ConsumerConfig: pubsub.TestConsumerConfig, + ModuleRoots: []string{}, + StreamTimeout: time.Minute, +} + +func ValidationServerConfigAddOptions(prefix string, f *pflag.FlagSet) { + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.StringSlice(prefix+".module-roots", nil, "Supported module root hashes") + f.Duration(prefix+".stream-timeout", DefaultValidationServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") +} + +func (cfg *ValidationServerConfig) Enabled() bool { + return cfg.RedisURL != "" +} diff --git a/validator/valnode/redis/consumer_test.go b/validator/valnode/redis/consumer_test.go new file mode 100644 index 000000000..0ebd697f1 --- /dev/null +++ b/validator/valnode/redis/consumer_test.go @@ -0,0 +1,30 @@ +package redis + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func TestTimeout(t *testing.T) { + handler := testhelpers.InitTestLog(t, log.LevelInfo) + ctx, cancel := context.WithCancel(context.Background()) + redisURL := redisutil.CreateTestRedis(ctx, t) + TestValidationServerConfig.RedisURL = redisURL + TestValidationServerConfig.ModuleRoots = []string{"0x123"} + TestValidationServerConfig.StreamTimeout = 100 * time.Millisecond + vs, err := NewValidationServer(&TestValidationServerConfig, nil) + if err != nil { + t.Fatalf("NewValidationSever() unexpected error: %v", err) + } + vs.Start(ctx) + time.Sleep(time.Second) + if !handler.WasLogged("Waiting for redis streams timed out") { + t.Error("Expected message about stream time-outs was not logged") + } + cancel() +} diff --git a/validator/server_api/validation_api.go b/validator/valnode/validation_api.go similarity index 82% rename from validator/server_api/validation_api.go rename to validator/valnode/validation_api.go index ca5aafcee..a67299b1a 100644 --- a/validator/server_api/validation_api.go +++ b/validator/valnode/validation_api.go @@ -1,4 +1,7 @@ -package server_api +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +package valnode import ( "context" @@ -12,11 +15,10 @@ import ( "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_arb" ) -const Namespace string = "validation" - type ValidationServerAPI struct { spawner validator.ValidationSpawner } @@ -29,8 +31,8 @@ func (a *ValidationServerAPI) Room() int { return a.spawner.Room() } -func (a *ValidationServerAPI) Validate(ctx context.Context, entry *ValidationInputJson, moduleRoot common.Hash) (validator.GoGlobalState, error) { - valInput, err := ValidationInputFromJson(entry) +func (a *ValidationServerAPI) Validate(ctx context.Context, entry *server_api.InputJSON, moduleRoot common.Hash) (validator.GoGlobalState, error) { + valInput, err := server_api.ValidationInputFromJson(entry) if err != nil { return validator.GoGlobalState{}, err } @@ -38,6 +40,10 @@ func (a *ValidationServerAPI) Validate(ctx context.Context, entry *ValidationInp return valRun.Await(ctx) } +func (a *ValidationServerAPI) WasmModuleRoots() ([]common.Hash, error) { + return a.spawner.WasmModuleRoots() +} + func NewValidationServerAPI(spawner validator.ValidationSpawner) *ValidationServerAPI { return &ValidationServerAPI{spawner} } @@ -69,8 +75,8 @@ func NewExecutionServerAPI(valSpawner validator.ValidationSpawner, execution val } } -func (a *ExecServerAPI) CreateExecutionRun(ctx context.Context, wasmModuleRoot common.Hash, jsonInput *ValidationInputJson) (uint64, error) { - input, err := ValidationInputFromJson(jsonInput) +func (a *ExecServerAPI) CreateExecutionRun(ctx context.Context, wasmModuleRoot common.Hash, jsonInput *server_api.InputJSON) (uint64, error) { + input, err := server_api.ValidationInputFromJson(jsonInput) if err != nil { return 0, err } @@ -107,8 +113,8 @@ func (a *ExecServerAPI) Start(ctx_in context.Context) { a.CallIteratively(a.removeOldRuns) } -func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *ValidationInputJson, expOut validator.GoGlobalState, moduleRoot common.Hash) error { - input, err := ValidationInputFromJson(jsonInput) +func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *server_api.InputJSON, expOut validator.GoGlobalState, moduleRoot common.Hash) error { + input, err := server_api.ValidationInputFromJson(jsonInput) if err != nil { return err } @@ -129,7 +135,7 @@ func (a *ExecServerAPI) getRun(id uint64) (validator.ExecutionRun, error) { return entry.run, nil } -func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position uint64) (*MachineStepResultJson, error) { +func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position uint64) (*server_api.MachineStepResultJson, error) { run, err := a.getRun(execid) if err != nil { return nil, err @@ -139,7 +145,7 @@ func (a *ExecServerAPI) GetStepAt(ctx context.Context, execid uint64, position u if err != nil { return nil, err } - return MachineStepResultToJson(res), nil + return server_api.MachineStepResultToJson(res), nil } func (a *ExecServerAPI) GetProofAt(ctx context.Context, execid uint64, position uint64) (string, error) { diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index ca954094f..972e11189 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -5,14 +5,15 @@ import ( "github.com/offchainlabs/nitro/validator" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - flag "github.com/spf13/pflag" - "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_arb" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/server_jit" + "github.com/offchainlabs/nitro/validator/valnode/redis" + "github.com/spf13/pflag" ) type WasmConfig struct { @@ -21,10 +22,10 @@ type WasmConfig struct { AllowedWasmModuleRoots []string `koanf:"allowed-wasm-module-roots"` } -func WasmConfigAddOptions(prefix string, f *flag.FlagSet) { +func WasmConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".root-path", DefaultWasmConfig.RootPath, "path to machine folders, each containing wasm files (machine.wavm.br, replay.wasm)") f.Bool(prefix+".enable-wasmroots-check", DefaultWasmConfig.EnableWasmrootsCheck, "enable check for compatibility of on-chain WASM module root with node") - f.StringSlice(prefix+".allowed-wasm-module-roots", DefaultWasmConfig.AllowedWasmModuleRoots, "list of WASM module roots to check if the on-chain WASM module root belongs to on node startup") + f.StringSlice(prefix+".allowed-wasm-module-roots", DefaultWasmConfig.AllowedWasmModuleRoots, "list of WASM module roots or mahcine base paths to match against on-chain WasmModuleRoot") } var DefaultWasmConfig = WasmConfig{ @@ -62,7 +63,7 @@ var TestValidationConfig = Config{ Wasm: DefaultWasmConfig, } -func ValidationConfigAddOptions(prefix string, f *flag.FlagSet) { +func ValidationConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".use-jit", DefaultValidationConfig.UseJit, "use jit for validation") f.Bool(prefix+".api-auth", DefaultValidationConfig.ApiAuth, "validate is an authenticated API") f.Bool(prefix+".api-public", DefaultValidationConfig.ApiPublic, "validate is a public API") @@ -75,6 +76,8 @@ type ValidationNode struct { config ValidationConfigFetcher arbSpawner *server_arb.ArbitratorSpawner jitSpawner *server_jit.JitSpawner + + redisConsumer *redis.ValidationServer } func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { @@ -103,7 +106,7 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod if err != nil { return nil, err } - var serverAPI *server_api.ExecServerAPI + var serverAPI *ExecServerAPI var jitSpawner *server_jit.JitSpawner if config.UseJit { jitConfigFetcher := func() *server_jit.JitSpawnerConfig { return &configFetcher().Jit } @@ -112,9 +115,17 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod if err != nil { return nil, err } - serverAPI = server_api.NewExecutionServerAPI(jitSpawner, arbSpawner, arbConfigFetcher) + serverAPI = NewExecutionServerAPI(jitSpawner, arbSpawner, arbConfigFetcher) } else { - serverAPI = server_api.NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) + serverAPI = NewExecutionServerAPI(arbSpawner, arbSpawner, arbConfigFetcher) + } + var redisConsumer *redis.ValidationServer + redisValidationConfig := arbConfigFetcher().RedisValidationServerConfig + if redisValidationConfig.Enabled() { + redisConsumer, err = redis.NewValidationServer(&redisValidationConfig, arbSpawner) + if err != nil { + log.Error("Creating new redis validation server", "error", err) + } } valAPIs := []rpc.API{{ Namespace: server_api.Namespace, @@ -125,7 +136,7 @@ func CreateValidationNode(configFetcher ValidationConfigFetcher, stack *node.Nod }} stack.RegisterAPIs(valAPIs) - return &ValidationNode{configFetcher, arbSpawner, jitSpawner}, nil + return &ValidationNode{configFetcher, arbSpawner, jitSpawner, redisConsumer}, nil } func (v *ValidationNode) Start(ctx context.Context) error { @@ -137,6 +148,9 @@ func (v *ValidationNode) Start(ctx context.Context) error { return err } } + if v.redisConsumer != nil { + v.redisConsumer.Start(ctx) + } return nil } diff --git a/wavmio/higher.go b/wavmio/higher.go index 81fa4a5e3..0fb5516c1 100644 --- a/wavmio/higher.go +++ b/wavmio/higher.go @@ -1,12 +1,14 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//go:build js -// +build js +//go:build wasm +// +build wasm package wavmio import ( + "unsafe" + "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/arbutil" ) @@ -22,14 +24,14 @@ const IDX_SEND_ROOT = 1 const IDX_INBOX_POSITION = 0 const IDX_POSITION_WITHIN_MESSAGE = 1 -func readBuffer(f func(uint32, []byte) uint32) []byte { +func readBuffer(f func(uint32, unsafe.Pointer) uint32) []byte { buf := make([]byte, 0, INITIAL_CAPACITY) offset := 0 for { if len(buf) < offset+QUERY_SIZE { buf = append(buf, make([]byte, offset+QUERY_SIZE-len(buf))...) } - read := f(uint32(offset), buf[offset:(offset+QUERY_SIZE)]) + read := f(uint32(offset), unsafe.Pointer(&buf[offset])) offset += int(read) if read < QUERY_SIZE { buf = buf[:offset] @@ -43,18 +45,19 @@ func StubInit() {} func StubFinal() {} func GetLastBlockHash() (hash common.Hash) { - getGlobalStateBytes32(IDX_LAST_BLOCKHASH, hash[:]) + hashUnsafe := unsafe.Pointer(&hash[0]) + getGlobalStateBytes32(IDX_LAST_BLOCKHASH, hashUnsafe) return } func ReadInboxMessage(msgNum uint64) []byte { - return readBuffer(func(offset uint32, buf []byte) uint32 { + return readBuffer(func(offset uint32, buf unsafe.Pointer) uint32 { return readInboxMessage(msgNum, offset, buf) }) } func ReadDelayedInboxMessage(seqNum uint64) []byte { - return readBuffer(func(offset uint32, buf []byte) uint32 { + return readBuffer(func(offset uint32, buf unsafe.Pointer) uint32 { return readDelayedInboxMessage(seqNum, offset, buf) }) } @@ -65,18 +68,21 @@ func AdvanceInboxMessage() { } func ResolveTypedPreimage(ty arbutil.PreimageType, hash common.Hash) ([]byte, error) { - return readBuffer(func(offset uint32, buf []byte) uint32 { - return resolveTypedPreimage(uint8(ty), hash[:], offset, buf) + return readBuffer(func(offset uint32, buf unsafe.Pointer) uint32 { + hashUnsafe := unsafe.Pointer(&hash[0]) + return resolveTypedPreimage(uint32(ty), hashUnsafe, offset, buf) }), nil } func SetLastBlockHash(hash [32]byte) { - setGlobalStateBytes32(IDX_LAST_BLOCKHASH, hash[:]) + hashUnsafe := unsafe.Pointer(&hash[0]) + setGlobalStateBytes32(IDX_LAST_BLOCKHASH, hashUnsafe) } // Note: if a GetSendRoot is ever modified, the validator will need to fill in the previous send root, which it currently does not. func SetSendRoot(hash [32]byte) { - setGlobalStateBytes32(IDX_SEND_ROOT, hash[:]) + hashUnsafe := unsafe.Pointer(&hash[0]) + setGlobalStateBytes32(IDX_SEND_ROOT, hashUnsafe) } func GetPositionWithinMessage() uint64 { diff --git a/wavmio/raw.go b/wavmio/raw.go index f0462cbbe..c09543f84 100644 --- a/wavmio/raw.go +++ b/wavmio/raw.go @@ -1,15 +1,30 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//go:build js -// +build js +//go:build wasm +// +build wasm package wavmio -func getGlobalStateBytes32(idx uint64, output []byte) -func setGlobalStateBytes32(idx uint64, val []byte) -func getGlobalStateU64(idx uint64) uint64 -func setGlobalStateU64(idx uint64, val uint64) -func readInboxMessage(msgNum uint64, offset uint32, output []byte) uint32 -func readDelayedInboxMessage(seqNum uint64, offset uint32, output []byte) uint32 -func resolveTypedPreimage(ty uint8, hash []byte, offset uint32, output []byte) uint32 +import "unsafe" + +//go:wasmimport wavmio getGlobalStateBytes32 +func getGlobalStateBytes32(idx uint32, output unsafe.Pointer) + +//go:wasmimport wavmio setGlobalStateBytes32 +func setGlobalStateBytes32(idx uint32, val unsafe.Pointer) + +//go:wasmimport wavmio getGlobalStateU64 +func getGlobalStateU64(idx uint32) uint64 + +//go:wasmimport wavmio setGlobalStateU64 +func setGlobalStateU64(idx uint32, val uint64) + +//go:wasmimport wavmio readInboxMessage +func readInboxMessage(msgNum uint64, offset uint32, output unsafe.Pointer) uint32 + +//go:wasmimport wavmio readDelayedInboxMessage +func readDelayedInboxMessage(seqNum uint64, offset uint32, output unsafe.Pointer) uint32 + +//go:wasmimport wavmio resolveTypedPreimage +func resolveTypedPreimage(ty uint32, hash unsafe.Pointer, offset uint32, output unsafe.Pointer) uint32 diff --git a/wavmio/raw.s b/wavmio/raw.s deleted file mode 100644 index 7347d1339..000000000 --- a/wavmio/raw.s +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -//go:build js -// +build js - -#include "textflag.h" - -TEXT ·getGlobalStateBytes32(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·setGlobalStateBytes32(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·getGlobalStateU64(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·setGlobalStateU64(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·readInboxMessage(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·readDelayedInboxMessage(SB), NOSPLIT, $0 - CallImport - RET - -TEXT ·resolveTypedPreimage(SB), NOSPLIT, $0 - CallImport - RET diff --git a/wavmio/stub.go b/wavmio/stub.go index 0893f3525..7fd29e206 100644 --- a/wavmio/stub.go +++ b/wavmio/stub.go @@ -1,8 +1,8 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//go:build !js -// +build !js +//go:build !wasm +// +build !wasm package wavmio diff --git a/wsbroadcastserver/clientconnection.go b/wsbroadcastserver/clientconnection.go index 6f5bf54e4..ba70756c9 100644 --- a/wsbroadcastserver/clientconnection.go +++ b/wsbroadcastserver/clientconnection.go @@ -302,7 +302,7 @@ func (cc *ClientConnection) Receive(ctx context.Context, timeout time.Duration) return msg, op, err } -// readRequests reads json-rpc request from connection. +// readRequest reads json-rpc request from connection. func (cc *ClientConnection) readRequest(ctx context.Context, timeout time.Duration) ([]byte, ws.OpCode, error) { cc.ioMutex.Lock() defer cc.ioMutex.Unlock()