Commit 64cd64c6 authored by vicotor's avatar vicotor

integrate process engine.

parent 847d835d
{ {
"AddressManager": "0x1FC2c049D25433585e7a029B9d59aE7fadf99059", "AddressManager": "0x38C86ee2Ef59682F1239fC2e76c01DD22a8E765F",
"AnchorStateRegistry": "0xDf22D5D292919a1963C5879682b91528De297d9d", "AnchorStateRegistry": "0x3AD9aEA9146305AA3fe100C24Ab5d113655402b5",
"AnchorStateRegistryProxy": "0xa8eb1f9072c0b7fbb4a26EEd4b1Ae7371522a3e0", "AnchorStateRegistryProxy": "0xA1a27C9E3A42E6ae5Aa71AB86c2847f623d2d58f",
"DelayedWETH": "0x782856f4dF1e34eEb1910FA12846ab15434E5F8d", "DelayedWETH": "0xf2ab45B561bfb98B93d8f60A264b94051e39CE99",
"DelayedWETHProxy": "0x2e5D80dc2e784569a48B6cBf775684003d58b420", "DelayedWETHProxy": "0x02c2C68f58899fD50d656aD9653427eB2eF8fc4F",
"DisputeGameFactory": "0x34fCb605772C0694256f88faDBEE98DBe3c5a75F", "DisputeGameFactory": "0x08370cF520A123C40b4F131EB2aF9CA126fB92F0",
"DisputeGameFactoryProxy": "0x3808f264E74688Bb3C442a9e31b5Df9bB0d79Df6", "DisputeGameFactoryProxy": "0xaa844f6Bd0c8DdD4b69FaB60454A99de32F617B1",
"L1CrossDomainMessenger": "0x524f0Cca359DfDDC07a706Cd3Af5d9C5c2AB8d21", "L1CrossDomainMessenger": "0xD521a240E8E326739851d87E98e9cCE1299c384A",
"L1CrossDomainMessengerProxy": "0xaDadd341d067873a11D388c3bA4CAa5C13409198", "L1CrossDomainMessengerProxy": "0x45e31d0219Ea35420561Ee878bc1e23824381886",
"L1ERC721Bridge": "0xE4FDa33401E0A9AD057f0F5845BFAf174CC8a651", "L1ERC721Bridge": "0xFa69Aa42cf8e63F05d01E7080Bb197BFEf8D58Eb",
"L1ERC721BridgeProxy": "0x746cfDD4Bf30501fcabf8B9E6f45A0791BB68E07", "L1ERC721BridgeProxy": "0xA6679bEbBEF6Ce20D747eDd3FB07f7E64d6E42d5",
"L1StandardBridge": "0x6fE6F9786a8227D4B458100ed4Aa042eA9BdE184", "L1StandardBridge": "0xF72ec99912E6cbBF9affdD2159D339e2B402BD55",
"L1StandardBridgeProxy": "0x27A27871Fb2176CDa268A73f18aB076d767b5279", "L1StandardBridgeProxy": "0x2B9c790A2AA3342aCc0ec474C8dB3Dee66bc65b3",
"L2OutputOracle": "0x52659a10213E87bB1FCAa0C81882Ecbc8FB52Cc8", "L2OutputOracle": "0x25bBC605d5eDfbe28D270b1AEC9Fd80165117275",
"L2OutputOracleProxy": "0xA98E22A5Da766E683C72ACf015766FA68623082F", "L2OutputOracleProxy": "0xde73FE0b1400412704a8682214B3F94d909E6793",
"Mips": "0xfa26D35c03902b8a57D1d0aA2eaAbDC8132FA3Af", "Mips": "0xF7F9bd0252b63683FA27d0D8bC8fb8DEac3752Fc",
"OptimismMintableERC20Factory": "0x454B9C65FAEf6F7Be36FCE560f2F230Ab08C053d", "OptimismMintableERC20Factory": "0x72Ba20B8e2b828bDBfB9E4a97DcF24b24a370439",
"OptimismMintableERC20FactoryProxy": "0x91cAeD9F6111aA8Ce0A01A885387b827C55addb3", "OptimismMintableERC20FactoryProxy": "0x1fb6B85E2e5f5f85F99b1a598A4ba6A8D645dC64",
"OptimismPortal": "0x9969f89a2419A9C431Ed7d5B70e8676Dc2c01774", "OptimismPortal": "0xa9e1fE9a7a580Ef6B913d3353882422C1c77fc4F",
"OptimismPortal2": "0xC1d786a28166028D4de2522fbcbaB551B12eF945", "OptimismPortal2": "0xA1A34cB7A8373614e66a482855417e2a8013b6c9",
"OptimismPortalProxy": "0xEa2E2269F5D0d327A740dCdC82f4E1c2da8453aF", "OptimismPortalProxy": "0x6FCF4305D69926688B311DCa84D16c82f196C0a6",
"PermissionedDelayedWETHProxy": "0x049149B03be673D0f8D645576D91F5e17E3A4202", "PermissionedDelayedWETHProxy": "0xc7907fDE17D5dA89004bc0c122BD251Db304E749",
"PreimageOracle": "0x3aC825F2b8252D78907cfcF2A3aB6fcf97B965D1", "PreimageOracle": "0x19c9587B5f8d733f96Ca274834e64381DE2D1C83",
"ProtocolVersions": "0xa0B066a10E948Ae74266173B231C8FAAC7FEA905", "ProtocolVersions": "0x4640D6a7F62cae7AE5F3f28B17c085b6f2Cfd730",
"ProtocolVersionsProxy": "0x49C1066E9CC5caB646226Cb79F588E39D77c2Fb5", "ProtocolVersionsProxy": "0xd3168476874b3eC093B37251D0D4D50D1f3E3530",
"ProxyAdmin": "0xD04E14a737B633ab89a4318348c52309c3818D86", "ProxyAdmin": "0x7375B5c3B3b3Cb5A7643ceBa07987B500c09EecC",
"SafeProxyFactory": "0x401D89657AbD2f27E386972D3003e7FDEB609D2e", "SafeProxyFactory": "0x66CB03A28d5E645f58bFDBcbCED35Fb1369918f0",
"SafeSingleton": "0xd8A2e86576a5757Bc17274d27CC2bB5f5079c4A0", "SafeSingleton": "0x55001679C3D561C4f5f4F203Ea61d5b0BcAD358A",
"SuperchainConfig": "0x83d71D09Db3F0A18D9dB08347EeB7297C6c424ce", "SuperchainConfig": "0x48Fe67C7adC998030d9b30bedaE8F992a9543620",
"SuperchainConfigProxy": "0x3CA9D5eE6B7C5302273c8b4749eAcF74e6C1440D", "SuperchainConfigProxy": "0x4A7F1642c97dFa24C9E8FA1F814785BA98C32c48",
"SystemConfig": "0x296f0682219444ad116B94d08a7bb65E85dbDbF6", "SystemConfig": "0x0F3127dA04f2B791DBf02ED34FAF7E0b473707D5",
"SystemConfigProxy": "0x42d8A1914B95CFB73d5879289304f9894d322d67", "SystemConfigProxy": "0xd9FdB8291f8ea076Fb5735eBF699131a8C7bB723",
"SystemOwnerSafe": "0xEBeDD9d6800600990337Dd8Fb0DA03BB077A5022" "SystemOwnerSafe": "0xb1E9984ea197F4b570B2dC12A6A62f78EeD8C602"
} }
\ No newline at end of file
...@@ -83,6 +83,6 @@ ...@@ -83,6 +83,6 @@
"daResolveWindow": 100, "daResolveWindow": 100,
"daBondSize": 1000, "daBondSize": 1000,
"daResolverRefundPercentage": 50, "daResolverRefundPercentage": 50,
"useCustomGasToken": true, "useCustomGasToken": false,
"customGasTokenAddress": "0x" "customGasTokenAddress": "0x88395111AB1586a4030dAC62a183542762929bbC"
} }
{ {
"chainId": 42069, "chainId": 42069,
"timestamp": 1740195604, "timestamp": 1741313556,
"extraData": "0x", "extraData": "0x",
"accounts": { "accounts": {
"0x905D5E8F7db76bCA91fdcA0990be7263dfD23335": { "0x905D5E8F7db76bCA91fdcA0990be7263dfD23335": {
......
{ {
"genesis": { "genesis": {
"l1": { "l1": {
"hash": "0x3bce58771dfabad1f4b3b4ab56cdbad02dfeddebf40a500362e2041584dbbc6d", "hash": "0x6422493f87d9d72d895b4813b782a8bb05f1c591d851b01ff82f809c82453247",
"number": 725 "number": 88365
}, },
"l2": { "l2": {
"hash": "0xe1bb0ba4acb1d5174539b0be5e94e753933d4a4dd58d6aa97b516ac983e5a20d", "hash": "0xee054561bf9bc7a73ac3c71c1df681284c55e42f9527414d0494573c9ed504e6",
"number": 0 "number": 0
}, },
"l2_time": 1740195604, "l2_time": 1741313556,
"system_config": { "system_config": {
"batcherAddr": "0x74fb49fb24700c896b6e68af0db872ac0cd97c0c", "batcherAddr": "0x74fb49fb24700c896b6e68af0db872ac0cd97c0c",
"overhead": "0x0000000000000000000000000000000000000000000000000000000000000000", "overhead": "0x0000000000000000000000000000000000000000000000000000000000000000",
...@@ -23,9 +23,10 @@ ...@@ -23,9 +23,10 @@
"l2_chain_id": 42069, "l2_chain_id": 42069,
"regolith_time": 0, "regolith_time": 0,
"canyon_time": 0, "canyon_time": 0,
"delta_time": 0,
"batch_inbox_address": "0xff00000000000000000000000000000000042069", "batch_inbox_address": "0xff00000000000000000000000000000000042069",
"deposit_contract_address": "0xea2e2269f5d0d327a740dcdc82f4e1c2da8453af", "deposit_contract_address": "0x6fcf4305d69926688b311dca84d16c82f196c0a6",
"l1_system_config_address": "0x42d8a1914b95cfb73d5879289304f9894d322d67", "l1_system_config_address": "0xd9fdb8291f8ea076fb5735ebf699131a8c7bb723",
"protocol_versions_address": "0x0000000000000000000000000000000000000000" "protocol_versions_address": "0x0000000000000000000000000000000000000000"
} }
module github.com/exchain/go-exchain module github.com/exchain/go-exchain
go 1.22.0 go 1.23.3
toolchain go1.22.7 toolchain go1.23.4
require ( require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/andybalholm/brotli v1.1.0 github.com/andybalholm/brotli v1.1.0
github.com/bmatcuk/doublestar/v4 v4.8.0
github.com/btcsuite/btcd v0.24.2 github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/cockroachdb/pebble v1.1.3 github.com/cockroachdb/pebble v1.1.3
github.com/consensys/gnark-crypto v0.12.1
github.com/crate-crypto/go-kzg-4844 v1.0.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20250115145553-996c7aba6565 github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20250115145553-996c7aba6565
github.com/ethereum/go-ethereum v1.14.12 github.com/ethereum/go-ethereum v1.15.0
github.com/fatih/color v1.18.0
github.com/fsnotify/fsnotify v1.8.0 github.com/fsnotify/fsnotify v1.8.0
github.com/go-task/slim-sprig/v3 v3.0.0
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8
...@@ -49,7 +44,7 @@ require ( ...@@ -49,7 +44,7 @@ require (
github.com/protolambda/ctxlock v0.1.0 github.com/protolambda/ctxlock v0.1.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.27.5 github.com/urfave/cli/v2 v2.27.5
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
golang.org/x/sync v0.10.0 golang.org/x/sync v0.10.0
golang.org/x/term v0.28.0 golang.org/x/term v0.28.0
...@@ -57,6 +52,14 @@ require ( ...@@ -57,6 +52,14 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require (
github.com/consensys/gnark-crypto v0.12.1 // indirect
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
)
require ( require (
github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e // indirect github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect
...@@ -87,55 +90,45 @@ require ( ...@@ -87,55 +90,45 @@ require (
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elastic/gosigar v0.14.3 // indirect github.com/elastic/gosigar v0.14.3 // indirect
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect
github.com/exchain/process v0.0.1
github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/fgprof v0.9.5 // indirect
github.com/ferranbt/fastssz v0.1.2 // indirect
github.com/flynn/noise v1.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ole/go-ole v1.3.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-yaml/yaml v2.1.0+incompatible // indirect github.com/go-yaml/yaml v2.1.0+incompatible // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang/glog v1.1.0 // indirect github.com/golang/protobuf v1.5.4
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gopacket v1.1.19 // indirect github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/graph-gophers/graphql-go v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect
github.com/hashicorp/go-hclog v1.6.2 // indirect github.com/hashicorp/go-hclog v1.6.2 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect github.com/hashicorp/golang-lru v0.5.0
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect
github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e // indirect github.com/hashicorp/raft-boltdb v0.0.0-20231211162105-6c830fa4535e // indirect
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/huin/goupnp v1.3.0 // indirect github.com/huin/goupnp v1.3.0 // indirect
github.com/influxdata/influxdb-client-go/v2 v2.4.0 // indirect
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
...@@ -181,13 +174,11 @@ require ( ...@@ -181,13 +174,11 @@ require (
github.com/multiformats/go-varint v0.0.7 // indirect github.com/multiformats/go-varint v0.0.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect github.com/nwaples/rardecode v1.1.3 // indirect
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1
github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/oklog/ulid/v2 v2.1.0
github.com/onsi/ginkgo/v2 v2.20.0 // indirect github.com/onsi/ginkgo/v2 v2.20.0 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pion/datachannel v1.5.8 // indirect github.com/pion/datachannel v1.5.8 // indirect
github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect
...@@ -219,11 +210,11 @@ require ( ...@@ -219,11 +210,11 @@ require (
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/supranational/blst v0.3.13 // indirect github.com/supranational/blst v0.3.13 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect
...@@ -239,15 +230,15 @@ require ( ...@@ -239,15 +230,15 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.21.0 // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/tools v0.26.0 // indirect
google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/grpc v1.57.1 // indirect google.golang.org/grpc v1.57.1
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.2
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect
...@@ -256,6 +247,8 @@ require ( ...@@ -256,6 +247,8 @@ require (
replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101412.0-rc.1 replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101412.0-rc.1
replace github.com/exchain/process => ../process
//replace github.com/ethereum/go-ethereum => ../op-geth //replace github.com/ethereum/go-ethereum => ../op-geth
// replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain // replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain
......
This diff is collapsed.
This diff is collapsed.
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/exchain/go-exchain/op-chain-ops/cmd/check-fjord/checks"
op_service "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/cliapp"
"github.com/exchain/go-exchain/op-service/ctxinterrupt"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli/v2"
)
var (
prefix = "CHECK_FJORD"
EndpointL2 = &cli.StringFlag{
Name: "l2",
Usage: "L2 execution RPC endpoint",
EnvVars: op_service.PrefixEnvVar(prefix, "L2"),
Value: "http://localhost:9545",
}
AccountKey = &cli.StringFlag{
Name: "account",
Usage: "Private key (hex-formatted string) of test account to perform test txs with",
EnvVars: op_service.PrefixEnvVar(prefix, "ACCOUNT"),
}
)
type CheckAction func(ctx context.Context, env *checks.CheckFjordConfig) error
func makeFlags() []cli.Flag {
flags := []cli.Flag{
EndpointL2,
AccountKey,
}
return append(flags, oplog.CLIFlags(prefix)...)
}
func makeCommand(name string, fn CheckAction) *cli.Command {
return &cli.Command{
Name: name,
Action: makeCommandAction(fn),
Flags: cliapp.ProtectFlags(makeFlags()),
}
}
func makeCommandAction(fn CheckAction) func(c *cli.Context) error {
return func(c *cli.Context) error {
logCfg := oplog.ReadCLIConfig(c)
logger := oplog.NewLogger(c.App.Writer, logCfg)
c.Context = ctxinterrupt.WithCancelOnInterrupt(c.Context)
l2Cl, err := ethclient.DialContext(c.Context, c.String(EndpointL2.Name))
if err != nil {
return fmt.Errorf("failed to dial L2 RPC: %w", err)
}
key, err := crypto.HexToECDSA(c.String(AccountKey.Name))
if err != nil {
return fmt.Errorf("failed to parse test private key: %w", err)
}
if err := fn(c.Context, &checks.CheckFjordConfig{
Log: logger,
L2: l2Cl,
Key: key,
Addr: crypto.PubkeyToAddress(key.PublicKey),
}); err != nil {
return fmt.Errorf("command error: %w", err)
}
return nil
}
}
func main() {
app := cli.NewApp()
app.Name = "check-fjord"
app.Usage = "Check Fjord upgrade results."
app.Description = "Check Fjord upgrade results."
app.Action = func(c *cli.Context) error {
return errors.New("see sub-commands")
}
app.Writer = os.Stdout
app.ErrWriter = os.Stderr
app.Commands = []*cli.Command{
makeCommand("all", checks.CheckAll),
makeCommand("rip-7212", checks.CheckRIP7212),
{
Name: "fast-lz",
Subcommands: []*cli.Command{
makeCommand("gas-price-oracle", checks.CheckGasPriceOracle),
makeCommand("tx-empty", checks.CheckTxEmpty),
makeCommand("tx-all-zero", checks.CheckTxAllZero),
makeCommand("tx-all-42", checks.CheckTxAll42),
makeCommand("tx-random", checks.CheckTxRandom),
makeCommand("all", checks.CheckAllFastLz),
},
Flags: makeFlags(),
Action: makeCommandAction(checks.CheckAllFastLz),
},
}
err := app.Run(os.Args)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Application failed: %v\n", err)
os.Exit(1)
}
}
package main
import (
"context"
"encoding/json"
"fmt"
"math/big"
"os"
"time"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts/metrics"
"github.com/exchain/go-exchain/op-challenger/game/types"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/clock"
"github.com/exchain/go-exchain/op-service/dial"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/sources/batching"
"github.com/exchain/go-exchain/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
"golang.org/x/exp/maps"
)
var (
factoryAddressFlag = &cli.StringFlag{
Name: "game-factory-address",
Usage: "Address of the fault game factory contract.",
Required: true,
}
l1EthRpcFlag = &cli.StringFlag{
Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1.",
Required: true,
}
gameAddressFlag = &cli.StringFlag{
Name: "game-address",
Usage: "Address of the FaultDisputeGame proxy contract to limit the search to.",
}
gameWindowFlag = &cli.DurationFlag{
Name: "game-window",
Usage: "The time window to limit the search of games containing unclaimed credit.",
}
)
func unclaimedCreditsApp(ctx *cli.Context) error {
logger := oplog.NewLogger(os.Stderr, oplog.DefaultCLIConfig())
oplog.SetGlobalLogHandler(logger.Handler())
rpcUrl := ctx.String(l1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", l1EthRpcFlag.Name)
}
factoryAddr, err := opservice.ParseAddress(ctx.String(factoryAddressFlag.Name))
if err != nil {
return err
}
gameWindow := ctx.Duration(gameWindowFlag.Name)
var gameAddr common.Address
if ctx.String(gameAddressFlag.Name) != "" {
gameAddr, err = opservice.ParseAddress(ctx.String(gameAddressFlag.Name))
if err != nil {
return err
}
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract := contracts.NewDisputeGameFactoryContract(metrics.NoopContractMetrics, factoryAddr, caller)
head, err := l1Client.HeaderByNumber(ctx.Context, nil)
if err != nil {
return fmt.Errorf("failed to retrieve current head block: %w", err)
}
return unclaimedCredits(ctx.Context, caller, contract, head.Hash(), gameWindow, gameAddr)
}
func unclaimedCredits(ctx context.Context, caller *batching.MultiCaller, factory *contracts.DisputeGameFactoryContract, block common.Hash, gameWindow time.Duration, gameFilter common.Address) error {
earliestTimestamp := clock.MinCheckedTimestamp(clock.SystemClock, gameWindow)
games, err := factory.GetGamesAtOrAfter(ctx, block, earliestTimestamp)
if err != nil {
return fmt.Errorf("failed to retrieve games: %w", err)
}
unclaimedCredits := make(map[common.Address]*big.Int)
for _, game := range games {
if (gameFilter != common.Address{}) && game.Proxy != gameFilter {
continue
}
gameContract, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, game.Proxy, caller)
if err != nil {
return fmt.Errorf("failed to create game contract: %w", err)
}
if status, err := gameContract.GetStatus(ctx); err != nil {
return err
} else if status == types.GameStatusInProgress {
continue
}
err = unclaimedCreditsForGame(ctx, gameContract, block, unclaimedCredits)
if err != nil {
return fmt.Errorf("failed to retrieve unclaimed credits for game: %w", err)
}
if game.Proxy == gameFilter {
break
}
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(unclaimedCredits)
}
func unclaimedCreditsForGame(ctx context.Context, game contracts.FaultDisputeGameContract, block common.Hash, unclaimedCredits map[common.Address]*big.Int) error {
claims, err := game.GetAllClaims(ctx, rpcblock.ByHash(block))
if err != nil {
return fmt.Errorf("failed to retrieve claims: %w", err)
}
players := make(map[common.Address]bool)
for _, claim := range claims {
players[claim.Claimant] = true
if claim.CounteredBy != (common.Address{}) {
players[claim.CounteredBy] = true
}
}
playerList := maps.Keys(players)
credits, err := game.GetCredits(ctx, rpcblock.Latest, playerList...)
if err != nil {
return fmt.Errorf("failed to retrieve credits: %w", err)
}
for i, credit := range credits {
player := playerList[i]
total := unclaimedCredits[player]
if total == nil {
total = new(big.Int)
unclaimedCredits[player] = total
}
total.Add(total, credit)
}
return nil
}
func main() {
app := &cli.App{
Name: "unclaimed-credits",
Description: "Outputs a JSON containing the unclaimed credits of each player of Fault Proofs. Only resolved games are considered.",
Flags: []cli.Flag{
l1EthRpcFlag,
factoryAddressFlag,
gameWindowFlag,
gameAddressFlag,
},
Action: unclaimedCreditsApp,
}
if err := app.Run(os.Args); err != nil {
log.Crit("error unclaimed-credits", "err", err)
}
}
bin
.fault-game-address
DEPRECATED_TARGETS := op-challenger clean test fuzz visualize
include ../justfiles/deprecated.mk
# op-challenger
The `op-challenger` is a modular **op-stack** challenge agent written in
golang for dispute games including, but not limited to, attestation games,
fault games, and validity games. To learn more about dispute games, visit
the [fault proof specs][proof-specs].
[proof-specs]: https://specs.optimism.io/experimental/fault-proof/index.html
## Quickstart
To build the `op-challenger`, run `make` (which executes the `make build`
[Makefile](./Makefile) target). To view a list of available commands and
options, run `./bin/op-challenger --help`.
## Usage
`op-challenger` is configurable via command line flags and environment
variables. The help menu shows the available config options and can be
accessed by running `./op-challenger --help`.
### Running with Cannon on Local Devnet
To run `op-challenger` against the local devnet, first clean and run
the devnet from the root of the repository.
```shell
make devnet-clean
make devnet-up
```
Then build the `op-challenger` with `make op-challenger`.
Run the `op-challenger` with:
```shell
DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json)
./op-challenger/bin/op-challenger \
--trace-type cannon \
--l1-eth-rpc http://localhost:8545 \
--rollup-rpc http://localhost:9546 \
--game-factory-address $DISPUTE_GAME_FACTORY \
--datadir temp/challenger-data \
--cannon-rollup-config .devnet/rollup.json \
--cannon-l2-genesis .devnet/genesis-l2.json \
--cannon-bin ./cannon/bin/cannon \
--cannon-server ./op-program/bin/op-program \
--cannon-prestate ./op-program/bin/prestate.bin.gz \
--l2-eth-rpc http://localhost:9545 \
--mnemonic "test test test test test test test test test test test junk" \
--hd-path "m/44'/60'/0'/0/8" \
--num-confirmations 1
```
The mnemonic and hd-path above is a prefunded address on the devnet.
The challenger will monitor dispute games and respond to any invalid
claims by posting the correct trace as the counter-claim. The commands
below can then be used to create and interact with games.
## Subcommands
The `op-challenger` has a few subcommands to interact with on-chain
fault dispute games. The subcommands support game creation, performing
game moves, and viewing fault dispute game data. They should not be
used in production and are intended to provide convenient manual testing.
### create-game
```shell
./bin/op-challenger create-game \
--l1-eth-rpc <L1_ETH_RPC> \
--game-factory-address <GAME_FACTORY_ADDRESS> \
--output-root <OUTPUT_ROOT> \
--l2-block-num <L2_BLOCK_NUM> \
<SIGNER_ARGS>
```
Starts a new fault dispute game that disputes the latest output proposal
in the L2 output oracle.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_FACTORY_ADDRESS` - the address of the dispute game factory contract on L1.
* `OUTPUT_ROOT` a hex encoded 32 byte hash that is used as the proposed output root.
* `L2_BLOCK_NUM` the L2 block number the proposed output root is from.
* `SIGNER_ARGS` arguments to specify the key to sign transactions with (e.g `--private-key`)
Optionally, you may specify the game type (aka "trace type") using the `--trace-type`
flag, which is set to the cannon trace type by default.
For known networks, the `--game-factory-address` option can be replaced by `--network`. See the `--help` output for a
list of predefined networks.
### move
The `move` subcommand can be run with either the `--attack` or `--defend` flag,
but not both.
```shell
./bin/op-challenger move \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS> \
--attack \
--parent-index <PARENT_INDEX> \
--claim <CLAIM> \
<SIGNER_ARGS>
```
Performs a move to either attack or defend the latest claim in the specified game.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to perform the move in.
* `(attack|defend)` - the type of move to make.
* `attack` indicates that the state hash in your local cannon trace differs to the state
hash included in the latest claim.
* `defend` indicates that the state hash in your local cannon trace matches the state hash
included in the latest claim.
* `PARENT_INDEX` - the index of the parent claim that will be countered by this new claim.
The special value of `latest` will counter the latest claim added to the game.
* `CLAIM` - the state hash to include in the counter-claim you are posting.
* `SIGNER_ARGS` arguments to specify the key to sign transactions with (e.g `--private-key`)
### resolve-claim
```shell
./bin/op-challenger resolve-claim \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS> \
--claim <CLAIM_INDEX> \
<SIGNER_ARGS>
```
Resolves a claim in a dispute game. Note that this will fail if the claim has already been resolved or if the claim is
not yet resolvable. If the claim is resolved successfully, the result is printed.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to resolve.
* `CLAIM_INDEX` - the index of the claim to resolve.
* `SIGNER_ARGS` arguments to specify the key to sign transactions with (e.g `--private-key`).
### resolve
```shell
./bin/op-challenger resolve \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS> \
<SIGNER_ARGS>
```
Resolves a dispute game. Note that this will fail if the dispute game has already
been resolved or if the clocks have not yet expired and further moves are possible.
If the game is resolved successfully, the result is printed.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to resolve.
* `SIGNER_ARGS` arguments to specify the key to sign transactions with (e.g `--private-key`).
### list-games
```shell
./bin/op-challenger list-games \
--l1-eth-rpc <L1_ETH_RPC> \
--game-factory-address <GAME_FACTORY_ADDRESS>
```
Prints the games created by the game factory along with their current status.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_FACTORY_ADDRESS` - the address of the dispute game factory contract on L1.
For known networks, the `--game-factory-address` option can be replaced by `--network`. See the `--help` output for a
list of predefined networks.
### list-claims
```shell
./bin/op-challenger list-claims \
--l1-eth-rpc <L1_ETH_RPC> \
--game-address <GAME_ADDRESS>
```
Prints the list of current claims in a dispute game.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `GAME_ADDRESS` - the address of the dispute game to list the move in.
### run-trace
```shell
./bin/op-challenger run-trace \
--network=<NETWORK_NAME> \
--l1-eth-rpc=<L1_ETH_RPC> \
--l1-beacon=<L1_BEACON> \
--l2-eth-rpc=<L2_ETH_RPC> \
--rollup-rpc=<ROLLUP_RPC> \
--datadir=<DATA_DIR> \
--prestates-url=<PRESTATES_URL> \
--run=<RUN_CONFIG>
```
* `NETWORK_NAME` - the name of a predefined L2 network.
* `L1_ETH_RPC` - the RPC endpoint of the L1 endpoint to use (e.g. `http://localhost:8545`).
* `L1_BEACON` - the REST endpoint of the L1 beacon node to use (e.g. `http://localhost:5100`).
* `L2_ETH_RPC` - the RPC endpoint of the L2 execution client to use
* `ROLLUP_RPC` - the RPC endpoint of the L2 consensus client to use
* `DATA_DIR` - the directory to use to store data
* `PRESTATES_URL` - the base URL to download required prestates from
* `RUN_CONFIG` - the trace providers and prestates to run. e.g. `cannon,asterisc-kona/kona-0.1.0-alpha.5/0x03c50fbef46a05f93ea7665fa89015c2108e10c1b4501799c0663774bd35a9c5`
Testing utility that continuously runs the specified trace providers against real chain data. The trace providers can be
configured with multiple different prestates. This allows testing both the current and potential future prestates with
the fault proofs virtual machine used by the trace provider.
The same CLI options as `op-challenger` itself are supported to configure the trace providers. The additional `--run`
option allows specifying which prestates to use. The format is `traceType/name/prestateHash` where traceType is the
trace type to use with the prestate (e.g cannon or asterisc-kona), name is an arbitrary name for the prestate to use
when reporting metrics and prestateHash is the hex encoded absolute prestate commitment to use. If name is omitted the
trace type name is used.If the prestateHash is omitted, the absolute prestate hash used for new games on-chain.
For example to run both the production cannon prestate and a custom
prestate, use `--run cannon,cannon/next-prestate/0x03c1f0d45248190f80430a4c31e24f8108f05f80ff8b16ecb82d20df6b1b43f3`.
package op_challenger
import (
"context"
"github.com/exchain/go-exchain/op-challenger/metrics"
"github.com/ethereum/go-ethereum/log"
"github.com/exchain/go-exchain/op-challenger/config"
"github.com/exchain/go-exchain/op-challenger/game"
"github.com/exchain/go-exchain/op-service/cliapp"
)
// Main is the programmatic entry-point for running op-challenger with a given configuration.
func Main(ctx context.Context, logger log.Logger, cfg *config.Config, m metrics.Metricer) (cliapp.Lifecycle, error) {
if err := cfg.Check(); err != nil {
return nil, err
}
return game.NewService(ctx, logger, cfg, m)
}
package op_challenger
import (
"context"
"testing"
"github.com/exchain/go-exchain/op-challenger/config"
"github.com/exchain/go-exchain/op-challenger/metrics"
"github.com/exchain/go-exchain/op-service/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestMainShouldReturnErrorWhenConfigInvalid(t *testing.T) {
cfg := &config.Config{}
app, err := Main(context.Background(), testlog.Logger(t, log.LevelInfo), cfg, metrics.NoopMetrics)
require.ErrorIs(t, err, cfg.Check())
require.Nil(t, app)
}
package main
import (
"context"
"fmt"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
contractMetrics "github.com/exchain/go-exchain/op-challenger/game/fault/contracts/metrics"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
"github.com/exchain/go-exchain/op-challenger/tools"
opservice "github.com/exchain/go-exchain/op-service"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/sources/batching"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var (
GameTypeFlag = &cli.StringFlag{
Name: "game-type",
Usage: "Game type to create (numeric values).",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "TRACE_TYPE"),
Value: types.CannonGameType.String(),
}
OutputRootFlag = &cli.StringFlag{
Name: "output-root",
Usage: "The output root for the fault dispute game.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "OUTPUT_ROOT"),
}
L2BlockNumFlag = &cli.StringFlag{
Name: "l2-block-num",
Usage: "The l2 block number for the game.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "L2_BLOCK_NUM"),
}
)
func CreateGame(ctx *cli.Context) error {
outputRoot := common.HexToHash(ctx.String(OutputRootFlag.Name))
gameType := ctx.Uint64(GameTypeFlag.Name)
l2BlockNum := ctx.Uint64(L2BlockNumFlag.Name)
contract, txMgr, err := NewContractWithTxMgr[*contracts.DisputeGameFactoryContract](ctx, flags.FactoryAddress,
func(ctx context.Context, metricer contractMetrics.ContractMetricer, address common.Address, caller *batching.MultiCaller) (*contracts.DisputeGameFactoryContract, error) {
return contracts.NewDisputeGameFactoryContract(metricer, address, caller), nil
})
if err != nil {
return fmt.Errorf("failed to create dispute game factory bindings: %w", err)
}
creator := tools.NewGameCreator(contract, txMgr)
gameAddr, err := creator.CreateGame(ctx.Context, outputRoot, gameType, l2BlockNum)
if err != nil {
return fmt.Errorf("failed to create game: %w", err)
}
fmt.Printf("Fetched Game Address: %s\n", gameAddr.String())
return nil
}
func createGameFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
flags.NetworkFlag,
flags.FactoryAddressFlag,
GameTypeFlag,
OutputRootFlag,
L2BlockNumFlag,
}
cliFlags = append(cliFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var CreateGameCommand = &cli.Command{
Name: "create-game",
Usage: "Creates a dispute game via the factory",
Description: "Creates a dispute game via the factory",
Action: Interruptible(CreateGame),
Flags: createGameFlags(),
}
package main
import (
"context"
"fmt"
"math/big"
"time"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts/metrics"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/dial"
"github.com/exchain/go-exchain/op-service/eth"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/sources/batching"
"github.com/exchain/go-exchain/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
"golang.org/x/exp/maps"
)
func ListCredits(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
gameAddr, err := opservice.ParseAddress(ctx.String(GameAddressFlag.Name))
if err != nil {
return err
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract, err := contracts.NewFaultDisputeGameContract(ctx.Context, metrics.NoopContractMetrics, gameAddr, caller)
if err != nil {
return err
}
return listCredits(ctx.Context, contract)
}
func listCredits(ctx context.Context, game contracts.FaultDisputeGameContract) error {
claims, err := game.GetAllClaims(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to load claims: %w", err)
}
metadata, err := game.GetGameMetadata(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to load metadata: %w", err)
}
recipients := make(map[common.Address]bool)
for _, claim := range claims {
if claim.CounteredBy != (common.Address{}) {
recipients[claim.CounteredBy] = true
}
recipients[claim.Claimant] = true
}
if metadata.L2BlockNumberChallenger != (common.Address{}) {
recipients[metadata.L2BlockNumberChallenger] = true
}
balance, withdrawalDelay, wethAddress, err := game.GetBalanceAndDelay(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to get DelayedWETH info: %w", err)
}
claimants := maps.Keys(recipients)
withdrawals, err := game.GetWithdrawals(ctx, rpcblock.Latest, claimants...)
if err != nil {
return fmt.Errorf("failed to get withdrawals: %w", err)
}
lineFormat := "%-42v %12v %-19v\n"
info := fmt.Sprintf(lineFormat, "Claimant", "ETH", "Unlock Time")
for i, withdrawal := range withdrawals {
var amount string
if withdrawal.Amount.Cmp(big.NewInt(0)) == 0 {
amount = "-"
} else {
amount = fmt.Sprintf("%12.8f", eth.WeiToEther(withdrawal.Amount))
}
var unlockTime string
if withdrawal.Timestamp.Cmp(big.NewInt(0)) == 0 {
unlockTime = "-"
} else {
unlockTime = time.Unix(withdrawal.Timestamp.Int64(), 0).Add(withdrawalDelay).Format(time.DateTime)
}
info += fmt.Sprintf(lineFormat, claimants[i], amount, unlockTime)
}
fmt.Printf("DelayedWETH Contract: %v • Total Balance (ETH): %12.8f • Delay: %v\n%v\n",
wethAddress, eth.WeiToEther(balance), withdrawalDelay, info)
return nil
}
func listCreditsFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
}
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var ListCreditsCommand = &cli.Command{
Name: "list-credits",
Usage: "List the credits in a dispute game",
Description: "Lists the credits in a dispute game",
Action: Interruptible(ListCredits),
Flags: listCreditsFlags(),
}
package main
import (
"context"
"fmt"
"math/big"
"strconv"
"time"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts/metrics"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
gameTypes "github.com/exchain/go-exchain/op-challenger/game/types"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/dial"
"github.com/exchain/go-exchain/op-service/eth"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/sources/batching"
"github.com/exchain/go-exchain/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var (
GameAddressFlag = &cli.StringFlag{
Name: "game-address",
Usage: "Address of the fault game contract.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "GAME_ADDRESS"),
}
VerboseFlag = &cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "Verbose output",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "VERBOSE"),
}
)
func ListClaims(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
gameAddr, err := opservice.ParseAddress(ctx.String(GameAddressFlag.Name))
if err != nil {
return err
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract, err := contracts.NewFaultDisputeGameContract(ctx.Context, metrics.NoopContractMetrics, gameAddr, caller)
if err != nil {
return err
}
return listClaims(ctx.Context, contract, ctx.Bool(VerboseFlag.Name))
}
func listClaims(ctx context.Context, game contracts.FaultDisputeGameContract, verbose bool) error {
metadata, err := game.GetGameMetadata(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to retrieve metadata: %w", err)
}
maxDepth, err := game.GetMaxGameDepth(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve max depth: %w", err)
}
maxClockDuration, err := game.GetMaxClockDuration(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve max clock duration: %w", err)
}
splitDepth, err := game.GetSplitDepth(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve split depth: %w", err)
}
status := metadata.Status
l2StartBlockNum, l2BlockNum, err := game.GetBlockRange(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve status: %w", err)
}
claims, err := game.GetAllClaims(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to retrieve claims: %w", err)
}
var resolutionTime time.Time
if status != gameTypes.GameStatusInProgress {
resolutionTime, err = game.GetResolvedAt(ctx, rpcblock.Latest)
if err != nil {
return fmt.Errorf("failed to retrieve resolved at: %w", err)
}
}
// The top game runs from depth 0 to split depth *inclusive*.
// The - 1 here accounts for the fact that the split depth is included in the top game.
bottomDepth := maxDepth - splitDepth - 1
resolved, err := game.IsResolved(ctx, rpcblock.Latest, claims...)
if err != nil {
return fmt.Errorf("failed to retrieve claim resolution: %w", err)
}
gameState := types.NewGameState(claims, maxDepth)
valueFormat := "%-14v"
if verbose {
valueFormat = "%-66v"
}
now := time.Now()
lineFormat := "%3v %-7v %6v %5v %14v " + valueFormat + " %-42v %12v %-19v %10v %v\n"
info := fmt.Sprintf(lineFormat, "Idx", "Move", "Parent", "Depth", "Trace", "Value", "Claimant", "Bond (ETH)", "Time", "Clock Used", "Resolution")
for i, claim := range claims {
pos := claim.Position
parent := strconv.Itoa(claim.ParentContractIndex)
var elapsed time.Duration // Root claim does not accumulate any time on its team's chess clock
if claim.IsRoot() {
parent = "-"
} else {
parentClaim, err := gameState.GetParent(claim)
if err != nil {
return fmt.Errorf("failed to retrieve parent claim: %w", err)
}
// Get the total chess clock time accumulated by the team that posted this claim at the time of the claim.
elapsed = gameState.ChessClock(claim.Clock.Timestamp, parentClaim)
}
var countered string
if !resolved[i] {
clock := gameState.ChessClock(now, claim)
resolvableAt := now.Add(maxClockDuration - clock).Format(time.DateTime)
countered = fmt.Sprintf("⏱️ %v", resolvableAt)
} else if claim.IsRoot() && metadata.L2BlockNumberChallenged {
countered = "❌ " + metadata.L2BlockNumberChallenger.Hex()
} else if claim.CounteredBy != (common.Address{}) {
countered = "❌ " + claim.CounteredBy.Hex()
} else {
countered = "✅"
}
move := "Attack"
if gameState.DefendsParent(claim) {
move = "Defend"
}
var traceIdx *big.Int
if claim.Depth() <= splitDepth {
traceIdx = claim.TraceIndex(splitDepth)
} else {
relativePos, err := claim.Position.RelativeToAncestorAtDepth(splitDepth + 1)
if err != nil {
fmt.Printf("Error calculating relative position for claim %v: %v", claim.ContractIndex, err)
traceIdx = big.NewInt(-1)
} else {
traceIdx = relativePos.TraceIndex(bottomDepth)
}
}
value := claim.Value.TerminalString()
if verbose {
value = claim.Value.Hex()
}
timestamp := claim.Clock.Timestamp.Format(time.DateTime)
bond := fmt.Sprintf("%12.8f", eth.WeiToEther(claim.Bond))
if verbose {
bond = fmt.Sprintf("%f", eth.WeiToEther(claim.Bond))
}
info = info + fmt.Sprintf(lineFormat,
i, move, parent, pos.Depth(), traceIdx, value, claim.Claimant, bond, timestamp, elapsed, countered)
}
blockNumChallenger := "Unchallenged"
if metadata.L2BlockNumberChallenged {
blockNumChallenger = "❌ " + metadata.L2BlockNumberChallenger.Hex()
}
statusStr := status.String()
if status != gameTypes.GameStatusInProgress {
statusStr = fmt.Sprintf("%v • Resolution Time: %v", statusStr, resolutionTime.Format(time.DateTime))
}
fmt.Printf("Status: %v • L2 Blocks: %v to %v (%v) • Split Depth: %v • Max Depth: %v • Claim Count: %v\n%v\n",
statusStr, l2StartBlockNum, l2BlockNum, blockNumChallenger, splitDepth, maxDepth, len(claims), info)
return nil
}
func listClaimsFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
VerboseFlag,
}
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var ListClaimsCommand = &cli.Command{
Name: "list-claims",
Usage: "List the claims in a dispute game",
Description: "Lists the claims in a dispute game",
Action: Interruptible(ListClaims),
Flags: listClaimsFlags(),
}
package main
import (
"cmp"
"context"
"fmt"
"slices"
"sync"
"time"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts/metrics"
"github.com/exchain/go-exchain/op-challenger/game/types"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/clock"
"github.com/exchain/go-exchain/op-service/dial"
openum "github.com/exchain/go-exchain/op-service/enum"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/sources/batching"
"github.com/exchain/go-exchain/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var ColumnTypes = []string{"time", "claimCount", "l2BlockNum"}
var (
SortByFlag = &cli.StringFlag{
Name: "sort-by",
Usage: "Sort games by column. Valid options: " + openum.EnumString(ColumnTypes),
Value: "time",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "SORT_BY"),
}
SortOrderFlag = &cli.StringFlag{
Name: "sort-order",
Usage: "Sort order for games. Valid options: 'asc' or 'desc'.",
Value: "asc",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "SORT_ORDER"),
}
)
func ListGames(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
factoryAddr, err := flags.FactoryAddress(ctx)
if err != nil {
return err
}
sortBy := ctx.String(SortByFlag.Name)
if sortBy != "" && !slices.Contains(ColumnTypes, sortBy) {
return fmt.Errorf("invalid sort-by value: %v", sortBy)
}
sortOrder := ctx.String(SortOrderFlag.Name)
if sortOrder != "" && sortOrder != "asc" && sortOrder != "desc" {
return fmt.Errorf("invalid sort-order value: %v", sortOrder)
}
gameWindow := ctx.Duration(flags.GameWindowFlag.Name)
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
contract := contracts.NewDisputeGameFactoryContract(metrics.NoopContractMetrics, factoryAddr, caller)
head, err := l1Client.HeaderByNumber(ctx.Context, nil)
if err != nil {
return fmt.Errorf("failed to retrieve current head block: %w", err)
}
return listGames(ctx.Context, caller, contract, head.Hash(), gameWindow, sortBy, sortOrder)
}
type gameInfo struct {
types.GameMetadata
claimCount uint64
l2BlockNum uint64
rootClaim common.Hash
status types.GameStatus
err error
}
func listGames(ctx context.Context, caller *batching.MultiCaller, factory *contracts.DisputeGameFactoryContract, block common.Hash, gameWindow time.Duration, sortBy, sortOrder string) error {
earliestTimestamp := clock.MinCheckedTimestamp(clock.SystemClock, gameWindow)
games, err := factory.GetGamesAtOrAfter(ctx, block, earliestTimestamp)
if err != nil {
return fmt.Errorf("failed to retrieve games: %w", err)
}
slices.Reverse(games)
infos := make([]gameInfo, len(games))
var wg sync.WaitGroup
for idx, game := range games {
gameContract, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, game.Proxy, caller)
if err != nil {
return fmt.Errorf("failed to create dispute game contract: %w", err)
}
info := gameInfo{GameMetadata: game}
infos[idx] = info
gameProxy := game.Proxy
currIndex := idx
wg.Add(1)
go func() {
defer wg.Done()
metadata, err := gameContract.GetGameMetadata(ctx, rpcblock.ByHash(block))
if err != nil {
info.err = fmt.Errorf("failed to retrieve metadata for game %v: %w", gameProxy, err)
return
}
infos[currIndex].status = metadata.Status
infos[currIndex].l2BlockNum = metadata.L2BlockNum
infos[currIndex].rootClaim = metadata.RootClaim
claimCount, err := gameContract.GetClaimCount(ctx)
if err != nil {
info.err = fmt.Errorf("failed to retrieve claim count for game %v: %w", gameProxy, err)
return
}
infos[currIndex].claimCount = claimCount
}()
}
wg.Wait()
lineFormat := "%3v %-42v %4v %-21v %14v %-66v %6v %-14v\n"
fmt.Printf(lineFormat, "Idx", "Game", "Type", "Created (Local)", "L2 Block", "Output Root", "Claims", "Status")
// Sort infos by the specified column
switch sortBy {
case "time":
slices.SortFunc(infos, func(i, j gameInfo) int {
if sortOrder == "desc" {
return cmp.Compare(j.Timestamp, i.Timestamp)
}
return cmp.Compare(i.Timestamp, j.Timestamp)
})
case "claimCount":
slices.SortFunc(infos, func(i, j gameInfo) int {
if sortOrder == "desc" {
return cmp.Compare(j.claimCount, i.claimCount)
}
return cmp.Compare(i.claimCount, j.claimCount)
})
case "l2BlockNum":
slices.SortFunc(infos, func(i, j gameInfo) int {
if sortOrder == "desc" {
return cmp.Compare(j.l2BlockNum, i.l2BlockNum)
}
return cmp.Compare(i.l2BlockNum, j.l2BlockNum)
})
}
for _, game := range infos {
if game.err != nil {
return game.err
}
created := time.Unix(int64(game.Timestamp), 0).Format(time.DateTime)
fmt.Printf(lineFormat,
game.Index, game.Proxy, game.GameType, created, game.l2BlockNum, game.rootClaim, game.claimCount, game.status)
}
return nil
}
func listGamesFlags() []cli.Flag {
cliFlags := []cli.Flag{
SortByFlag,
SortOrderFlag,
flags.L1EthRpcFlag,
flags.NetworkFlag,
flags.FactoryAddressFlag,
flags.GameWindowFlag,
}
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var ListGamesCommand = &cli.Command{
Name: "list-games",
Usage: "List the games created by a dispute game factory",
Description: "Lists the games created by a dispute game factory",
Action: Interruptible(ListGames),
Flags: listGamesFlags(),
}
package main
import (
"context"
"os"
"github.com/exchain/go-exchain/op-challenger/metrics"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log"
challenger "github.com/exchain/go-exchain/op-challenger"
"github.com/exchain/go-exchain/op-challenger/config"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/version"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/cliapp"
"github.com/exchain/go-exchain/op-service/ctxinterrupt"
oplog "github.com/exchain/go-exchain/op-service/log"
)
var (
GitCommit = ""
GitDate = ""
)
// VersionWithMeta holds the textual version string including the metadata.
var VersionWithMeta = opservice.FormatVersion(version.Version, GitCommit, GitDate, version.Meta)
func main() {
args := os.Args
ctx := ctxinterrupt.WithSignalWaiterMain(context.Background())
if err := run(ctx, args, func(ctx context.Context, l log.Logger, config *config.Config) (cliapp.Lifecycle, error) {
return challenger.Main(ctx, l, config, metrics.NewMetrics())
}); err != nil {
log.Crit("Application failed", "err", err)
}
}
type ConfiguredLifecycle func(ctx context.Context, log log.Logger, config *config.Config) (cliapp.Lifecycle, error)
func run(ctx context.Context, args []string, action ConfiguredLifecycle) error {
oplog.SetupDefaults()
app := cli.NewApp()
app.Version = VersionWithMeta
app.Flags = cliapp.ProtectFlags(flags.Flags)
app.Name = "op-challenger"
app.Usage = "Challenge outputs"
app.Description = "Ensures that on chain outputs are correct."
app.Commands = []*cli.Command{
ListGamesCommand,
ListClaimsCommand,
ListCreditsCommand,
CreateGameCommand,
MoveCommand,
ResolveCommand,
ResolveClaimCommand,
RunTraceCommand,
}
app.Action = cliapp.LifecycleCmd(func(ctx *cli.Context, close context.CancelCauseFunc) (cliapp.Lifecycle, error) {
logger, err := setupLogging(ctx)
if err != nil {
return nil, err
}
logger.Info("Starting op-challenger", "version", VersionWithMeta)
cfg, err := flags.NewConfigFromCLI(ctx, logger)
if err != nil {
return nil, err
}
return action(ctx.Context, logger, cfg)
})
return app.RunContext(ctx, args)
}
func setupLogging(ctx *cli.Context) (log.Logger, error) {
logCfg := oplog.ReadCLIConfig(ctx)
logger := oplog.NewLogger(oplog.AppOut(ctx), logCfg)
oplog.SetGlobalLogHandler(logger.Handler())
return logger, nil
}
This diff is collapsed.
package main
import (
"fmt"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
opservice "github.com/exchain/go-exchain/op-service"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var (
AttackFlag = &cli.BoolFlag{
Name: "attack",
Usage: "An attack move. If true, the defend flag must not be set.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "ATTACK"),
}
DefendFlag = &cli.BoolFlag{
Name: "defend",
Usage: "A defending move. If true, the attack flag must not be set.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "DEFEND"),
}
ParentIndexFlag = &cli.StringFlag{
Name: "parent-index",
Usage: "The index of the claim to move on.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "PARENT_INDEX"),
}
ClaimFlag = &cli.StringFlag{
Name: "claim",
Usage: "The claim hash.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "CLAIM"),
}
)
func Move(ctx *cli.Context) error {
attack := ctx.Bool(AttackFlag.Name)
defend := ctx.Bool(DefendFlag.Name)
parentIndex := ctx.Uint64(ParentIndexFlag.Name)
claim := common.HexToHash(ctx.String(ClaimFlag.Name))
if attack && defend {
return fmt.Errorf("both attack and defense flags cannot be set")
}
contract, txMgr, err := NewContractWithTxMgr[contracts.FaultDisputeGameContract](ctx, AddrFromFlag(GameAddressFlag.Name), contracts.NewFaultDisputeGameContract)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
parentClaim, err := contract.GetClaim(ctx.Context, parentIndex)
if err != nil {
return fmt.Errorf("failed to get parent claim: %w", err)
}
var tx txmgr.TxCandidate
if attack {
tx, err = contract.AttackTx(ctx.Context, parentClaim, claim)
if err != nil {
return fmt.Errorf("failed to create attack tx: %w", err)
}
} else if defend {
tx, err = contract.DefendTx(ctx.Context, parentClaim, claim)
if err != nil {
return fmt.Errorf("failed to create defense tx: %w", err)
}
} else {
return fmt.Errorf("either attack or defense flag must be set")
}
rct, err := txMgr.Send(ctx.Context, tx)
if err != nil {
return fmt.Errorf("failed to send tx: %w", err)
}
fmt.Printf("Sent tx with status: %v, hash: %s\n", rct.Status, rct.TxHash.String())
return nil
}
func moveFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
AttackFlag,
DefendFlag,
ParentIndexFlag,
ClaimFlag,
}
cliFlags = append(cliFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var MoveCommand = &cli.Command{
Name: "move",
Usage: "Creates and sends a move transaction to the dispute game",
Description: "Creates and sends a move transaction to the dispute game",
Action: Interruptible(Move),
Flags: moveFlags(),
}
package main
import (
"context"
"fmt"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/urfave/cli/v2"
)
func Resolve(ctx *cli.Context) error {
contract, txMgr, err := NewContractWithTxMgr[contracts.FaultDisputeGameContract](ctx, AddrFromFlag(GameAddressFlag.Name), contracts.NewFaultDisputeGameContract)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
tx, err := contract.ResolveTx()
if err != nil {
return fmt.Errorf("failed to create resolve tx: %w", err)
}
rct, err := txMgr.Send(context.Background(), tx)
if err != nil {
return fmt.Errorf("failed to send tx: %w", err)
}
fmt.Printf("Sent resolve tx with status: %v, hash: %s\n", rct.Status, rct.TxHash.String())
return nil
}
func resolveFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
}
cliFlags = append(cliFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var ResolveCommand = &cli.Command{
Name: "resolve",
Usage: "Resolves the specified dispute game if possible",
Description: "Resolves the specified dispute game if possible",
Action: Interruptible(Resolve),
Flags: resolveFlags(),
}
package main
import (
"context"
"fmt"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/contracts"
opservice "github.com/exchain/go-exchain/op-service"
oplog "github.com/exchain/go-exchain/op-service/log"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/urfave/cli/v2"
)
var (
ClaimIdxFlag = &cli.Uint64Flag{
Name: "claim",
Usage: "Index of the claim to resolve.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "CLAIM"),
}
)
func ResolveClaim(ctx *cli.Context) error {
if !ctx.IsSet(ClaimIdxFlag.Name) {
return fmt.Errorf("must specify %v flag", ClaimIdxFlag.Name)
}
idx := ctx.Uint64(ClaimIdxFlag.Name)
contract, txMgr, err := NewContractWithTxMgr[contracts.FaultDisputeGameContract](ctx, AddrFromFlag(GameAddressFlag.Name), contracts.NewFaultDisputeGameContract)
if err != nil {
return fmt.Errorf("failed to create dispute game bindings: %w", err)
}
err = contract.CallResolveClaim(ctx.Context, idx)
if err != nil {
return fmt.Errorf("claim is not resolvable: %w", err)
}
tx, err := contract.ResolveClaimTx(idx)
if err != nil {
return fmt.Errorf("failed to create resolve claim tx: %w", err)
}
rct, err := txMgr.Send(context.Background(), tx)
if err != nil {
return fmt.Errorf("failed to send tx: %w", err)
}
fmt.Printf("Sent resolve claim tx with status: %v, hash: %s\n", rct.Status, rct.TxHash.String())
return nil
}
func resolveClaimFlags() []cli.Flag {
cliFlags := []cli.Flag{
flags.L1EthRpcFlag,
GameAddressFlag,
ClaimIdxFlag,
}
cliFlags = append(cliFlags, txmgr.CLIFlagsWithDefaults(flags.EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...)
cliFlags = append(cliFlags, oplog.CLIFlags(flags.EnvVarPrefix)...)
return cliFlags
}
var ResolveClaimCommand = &cli.Command{
Name: "resolve-claim",
Usage: "Resolves the specified claim if possible",
Description: "Resolves the specified claim if possible",
Action: Interruptible(ResolveClaim),
Flags: resolveClaimFlags(),
}
package main
import (
"context"
"errors"
"fmt"
"slices"
"strings"
"github.com/exchain/go-exchain/op-challenger/flags"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
"github.com/exchain/go-exchain/op-challenger/runner"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/cliapp"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
var (
ErrUnknownTraceType = errors.New("unknown trace type")
ErrInvalidPrestateHash = errors.New("invalid prestate hash")
)
func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) {
logger, err := setupLogging(ctx)
if err != nil {
return nil, err
}
logger.Info("Starting trace runner", "version", VersionWithMeta)
cfg, err := flags.NewConfigFromCLI(ctx, logger)
if err != nil {
return nil, err
}
if err := cfg.Check(); err != nil {
return nil, err
}
runConfigs, err := parseRunArgs(ctx.StringSlice(RunTraceRunFlag.Name))
if err != nil {
return nil, err
}
if len(runConfigs) == 0 {
// Default to running on-chain version of each enabled trace type
for _, traceType := range cfg.TraceTypes {
runConfigs = append(runConfigs, runner.RunConfig{TraceType: traceType})
}
}
return runner.NewRunner(logger, cfg, runConfigs), nil
}
func runTraceFlags() []cli.Flag {
return append(flags.Flags, RunTraceRunFlag)
}
var RunTraceCommand = &cli.Command{
Name: "run-trace",
Usage: "Continuously runs the specified trace providers in a regular loop",
Description: "Runs trace providers against real chain data to confirm compatibility",
Action: cliapp.LifecycleCmd(RunTrace),
Flags: runTraceFlags(),
}
var (
RunTraceRunFlag = &cli.StringSliceFlag{
Name: "run",
Usage: "Specify a trace to run. Format is traceType/name/prestateHash where " +
"traceType is the trace type to use with the prestate (e.g cannon or asterisc-kona), " +
"name is an arbitrary name for the prestate to use when reporting metrics and" +
"prestateHash is the hex encoded absolute prestate commitment to use. " +
"If name is omitted the trace type name is used." +
"If the prestateHash is omitted, the absolute prestate hash used for new games on-chain.",
EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "RUN"),
}
)
func parseRunArgs(args []string) ([]runner.RunConfig, error) {
cfgs := make([]runner.RunConfig, len(args))
for i, arg := range args {
cfg, err := parseRunArg(arg)
if err != nil {
return nil, err
}
cfgs[i] = cfg
}
return cfgs, nil
}
func parseRunArg(arg string) (runner.RunConfig, error) {
cfg := runner.RunConfig{}
opts := strings.SplitN(arg, "/", 3)
if len(opts) == 0 {
return runner.RunConfig{}, fmt.Errorf("invalid run config %q", arg)
}
cfg.TraceType = types.TraceType(opts[0])
if !slices.Contains(types.TraceTypes, cfg.TraceType) {
return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrUnknownTraceType, opts[0], arg)
}
if len(opts) > 1 {
cfg.Name = opts[1]
} else {
cfg.Name = cfg.TraceType.String()
}
if len(opts) > 2 {
cfg.Prestate = common.HexToHash(opts[2])
if cfg.Prestate == (common.Hash{}) {
return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrInvalidPrestateHash, opts[2], arg)
}
}
return cfg, nil
}
package main
import (
"strings"
"testing"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
"github.com/exchain/go-exchain/op-challenger/runner"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestParseRunArg(t *testing.T) {
tests := []struct {
arg string
expected runner.RunConfig
err error
}{
{arg: "unknown/test1/0x1234", err: ErrUnknownTraceType},
{arg: "cannon", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: types.TraceTypeCannon.String()}},
{arg: "asterisc", expected: runner.RunConfig{TraceType: types.TraceTypeAsterisc, Name: types.TraceTypeAsterisc.String()}},
{arg: "cannon/test1", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1"}},
{arg: "cannon/test1/0x1234", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1", Prestate: common.HexToHash("0x1234")}},
{arg: "cannon/test1/invalid", err: ErrInvalidPrestateHash},
}
for _, test := range tests {
test := test
// Slash characters in test names confuse some things that parse the output as it looks like a subtest
t.Run(strings.ReplaceAll(test.arg, "/", "_"), func(t *testing.T) {
actual, err := parseRunArg(test.arg)
require.ErrorIs(t, err, test.err)
require.Equal(t, test.expected, actual)
})
}
}
package main
import (
"context"
"fmt"
"github.com/exchain/go-exchain/op-challenger/flags"
contractMetrics "github.com/exchain/go-exchain/op-challenger/game/fault/contracts/metrics"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/ctxinterrupt"
"github.com/exchain/go-exchain/op-service/dial"
"github.com/exchain/go-exchain/op-service/sources/batching"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/exchain/go-exchain/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
)
type ContractCreator[T any] func(context.Context, contractMetrics.ContractMetricer, common.Address, *batching.MultiCaller) (T, error)
func Interruptible(action cli.ActionFunc) cli.ActionFunc {
return func(ctx *cli.Context) error {
ctx.Context = ctxinterrupt.WithCancelOnInterrupt(ctx.Context)
return action(ctx)
}
}
func AddrFromFlag(flagName string) func(ctx *cli.Context) (common.Address, error) {
return func(ctx *cli.Context) (common.Address, error) {
gameAddr, err := opservice.ParseAddress(ctx.String(flagName))
if err != nil {
return common.Address{}, err
}
return gameAddr, nil
}
}
// NewContractWithTxMgr creates a new contract and a transaction manager.
func NewContractWithTxMgr[T any](ctx *cli.Context, getAddr func(ctx *cli.Context) (common.Address, error), creator ContractCreator[T]) (T, txmgr.TxManager, error) {
var contract T
caller, txMgr, err := newClientsFromCLI(ctx)
if err != nil {
return contract, nil, err
}
created, err := newContractFromCLI(ctx, getAddr, caller, creator)
if err != nil {
return contract, nil, err
}
return created, txMgr, nil
}
// newContractFromCLI creates a new contract from the CLI context.
func newContractFromCLI[T any](ctx *cli.Context, getAddr func(ctx *cli.Context) (common.Address, error), caller *batching.MultiCaller, creator ContractCreator[T]) (T, error) {
var contract T
gameAddr, err := getAddr(ctx)
if err != nil {
return contract, err
}
created, err := creator(ctx.Context, contractMetrics.NoopContractMetrics, gameAddr, caller)
if err != nil {
return contract, fmt.Errorf("failed to create contract bindings: %w", err)
}
return created, nil
}
// newClientsFromCLI creates a new caller and transaction manager from the CLI context.
func newClientsFromCLI(ctx *cli.Context) (*batching.MultiCaller, txmgr.TxManager, error) {
logger, err := setupLogging(ctx)
if err != nil {
return nil, nil, err
}
rpcUrl := ctx.String(flags.L1EthRpcFlag.Name)
if rpcUrl == "" {
return nil, nil, fmt.Errorf("missing %v", flags.L1EthRpcFlag.Name)
}
l1Client, err := dial.DialEthClientWithTimeout(ctx.Context, dial.DefaultDialTimeout, logger, rpcUrl)
if err != nil {
return nil, nil, fmt.Errorf("failed to dial L1: %w", err)
}
defer l1Client.Close()
caller := batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize)
txMgrConfig := txmgr.ReadCLIConfig(ctx)
txMgr, err := txmgr.NewSimpleTxManager("challenger", logger, &metrics.NoopTxMetrics{}, txMgrConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to create the transaction manager: %w", err)
}
logger.Info("Configured transaction manager", "sender", txMgr.From())
return caller, txMgr, nil
}
package config
import (
"errors"
"fmt"
"net/url"
"runtime"
"slices"
"time"
"github.com/exchain/go-exchain/op-challenger/game/fault/trace/vm"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
opmetrics "github.com/exchain/go-exchain/op-service/metrics"
"github.com/exchain/go-exchain/op-service/oppprof"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
var (
ErrMissingTraceType = errors.New("no supported trace types specified")
ErrMissingDatadir = errors.New("missing datadir")
ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0")
ErrMissingL2Rpc = errors.New("missing L2 rpc url")
ErrMissingCannonAbsolutePreState = errors.New("missing cannon absolute pre-state")
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingL1Beacon = errors.New("missing l1 beacon url")
ErrMissingGameFactoryAddress = errors.New("missing game factory address")
ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq")
ErrMissingCannonInfoFreq = errors.New("missing cannon info freq")
ErrMissingRollupRpc = errors.New("missing rollup rpc url")
ErrMissingSupervisorRpc = errors.New("missing supervisor rpc url")
ErrMissingAsteriscAbsolutePreState = errors.New("missing asterisc absolute pre-state")
ErrMissingAsteriscSnapshotFreq = errors.New("missing asterisc snapshot freq")
ErrMissingAsteriscInfoFreq = errors.New("missing asterisc info freq")
ErrMissingAsteriscKonaAbsolutePreState = errors.New("missing asterisc kona absolute pre-state")
ErrMissingAsteriscKonaSnapshotFreq = errors.New("missing asterisc kona snapshot freq")
ErrMissingAsteriscKonaInfoFreq = errors.New("missing asterisc kona info freq")
)
const (
DefaultPollInterval = time.Second * 12
DefaultCannonSnapshotFreq = uint(1_000_000_000)
DefaultCannonInfoFreq = uint(10_000_000)
DefaultAsteriscSnapshotFreq = uint(1_000_000_000)
DefaultAsteriscInfoFreq = uint(10_000_000)
// DefaultGameWindow is the default maximum time duration in the past
// that the challenger will look for games to progress.
// The default value is 28 days. The worst case duration for a game is 16 days
// (due to clock extension), plus 7 days WETH withdrawal delay leaving a 5 day
// buffer to monitor games to ensure bonds are claimed.
DefaultGameWindow = 28 * 24 * time.Hour
DefaultMaxPendingTx = 10
)
// Config is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services.
// It is used to initialize the challenger.
type Config struct {
L1EthRpc string // L1 RPC Url
L1Beacon string // L1 Beacon API Url
GameFactoryAddress common.Address // Address of the dispute game factory
GameAllowlist []common.Address // Allowlist of fault game addresses
GameWindow time.Duration // Maximum time duration to look for games to progress
Datadir string // Data Directory
MaxConcurrency uint // Maximum number of threads to use when progressing games
PollInterval time.Duration // Polling interval for latest-block subscription when using an HTTP RPC provider
AllowInvalidPrestate bool // Whether to allow responding to games where the prestate does not match
AdditionalBondClaimants []common.Address // List of addresses to claim bonds for in addition to the tx manager sender
SelectiveClaimResolution bool // Whether to only resolve claims for the claimants in AdditionalBondClaimants union [TxSender.From()]
TraceTypes []types.TraceType // Type of traces supported
RollupRpc string // L2 Rollup RPC Url
SupervisorRPC string // L2 supervisor RPC URL
L2Rpcs []string // L2 RPC Url
// Specific to the cannon trace provider
Cannon vm.Config
CannonAbsolutePreState string // File to load the absolute pre-state for Cannon traces from
CannonAbsolutePreStateBaseURL *url.URL // Base URL to retrieve absolute pre-states for Cannon traces from
// Specific to the asterisc trace provider
Asterisc vm.Config
AsteriscAbsolutePreState string // File to load the absolute pre-state for Asterisc traces from
AsteriscAbsolutePreStateBaseURL *url.URL // Base URL to retrieve absolute pre-states for Asterisc traces from
AsteriscKona vm.Config
AsteriscKonaAbsolutePreState string // File to load the absolute pre-state for AsteriscKona traces from
AsteriscKonaAbsolutePreStateBaseURL *url.URL // Base URL to retrieve absolute pre-states for AsteriscKona traces from
MaxPendingTx uint64 // Maximum number of pending transactions (0 == no limit)
TxMgrConfig txmgr.CLIConfig
MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig
}
func NewConfig(
gameFactoryAddress common.Address,
l1EthRpc string,
l1BeaconApi string,
l2RollupRpc string,
l2EthRpc string,
datadir string,
supportedTraceTypes ...types.TraceType,
) Config {
return Config{
L1EthRpc: l1EthRpc,
L1Beacon: l1BeaconApi,
RollupRpc: l2RollupRpc,
L2Rpcs: []string{l2EthRpc},
GameFactoryAddress: gameFactoryAddress,
MaxConcurrency: uint(runtime.NumCPU()),
PollInterval: DefaultPollInterval,
TraceTypes: supportedTraceTypes,
MaxPendingTx: DefaultMaxPendingTx,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc, txmgr.DefaultChallengerFlagValues),
MetricsConfig: opmetrics.DefaultCLIConfig(),
PprofConfig: oppprof.DefaultCLIConfig(),
Datadir: datadir,
Cannon: vm.Config{
VmType: types.TraceTypeCannon,
L1: l1EthRpc,
L1Beacon: l1BeaconApi,
L2s: []string{l2EthRpc},
SnapshotFreq: DefaultCannonSnapshotFreq,
InfoFreq: DefaultCannonInfoFreq,
DebugInfo: true,
BinarySnapshots: true,
},
Asterisc: vm.Config{
VmType: types.TraceTypeAsterisc,
L1: l1EthRpc,
L1Beacon: l1BeaconApi,
L2s: []string{l2EthRpc},
SnapshotFreq: DefaultAsteriscSnapshotFreq,
InfoFreq: DefaultAsteriscInfoFreq,
BinarySnapshots: true,
},
AsteriscKona: vm.Config{
VmType: types.TraceTypeAsteriscKona,
L1: l1EthRpc,
L1Beacon: l1BeaconApi,
L2s: []string{l2EthRpc},
SnapshotFreq: DefaultAsteriscSnapshotFreq,
InfoFreq: DefaultAsteriscInfoFreq,
BinarySnapshots: true,
},
GameWindow: DefaultGameWindow,
}
}
func (c Config) TraceTypeEnabled(t types.TraceType) bool {
return slices.Contains(c.TraceTypes, t)
}
func (c Config) Check() error {
if c.L1EthRpc == "" {
return ErrMissingL1EthRPC
}
if c.L1Beacon == "" {
return ErrMissingL1Beacon
}
if len(c.L2Rpcs) == 0 {
return ErrMissingL2Rpc
}
if c.GameFactoryAddress == (common.Address{}) {
return ErrMissingGameFactoryAddress
}
if len(c.TraceTypes) == 0 {
return ErrMissingTraceType
}
if c.Datadir == "" {
return ErrMissingDatadir
}
if c.MaxConcurrency == 0 {
return ErrMaxConcurrencyZero
}
if c.TraceTypeEnabled(types.TraceTypeSuperCannon) {
if c.SupervisorRPC == "" {
return ErrMissingSupervisorRpc
}
if err := c.validateBaseCannonOptions(); err != nil {
return err
}
}
if c.TraceTypeEnabled(types.TraceTypeCannon) || c.TraceTypeEnabled(types.TraceTypePermissioned) {
if c.RollupRpc == "" {
return ErrMissingRollupRpc
}
if err := c.validateBaseCannonOptions(); err != nil {
return err
}
}
if c.TraceTypeEnabled(types.TraceTypeAsterisc) {
if c.RollupRpc == "" {
return ErrMissingRollupRpc
}
if err := c.Asterisc.Check(); err != nil {
return fmt.Errorf("asterisc: %w", err)
}
if c.AsteriscAbsolutePreState == "" && c.AsteriscAbsolutePreStateBaseURL == nil {
return ErrMissingAsteriscAbsolutePreState
}
if c.Asterisc.SnapshotFreq == 0 {
return ErrMissingAsteriscSnapshotFreq
}
if c.Asterisc.InfoFreq == 0 {
return ErrMissingAsteriscInfoFreq
}
}
if c.TraceTypeEnabled(types.TraceTypeAsteriscKona) {
if c.RollupRpc == "" {
return ErrMissingRollupRpc
}
if err := c.AsteriscKona.Check(); err != nil {
return fmt.Errorf("asterisc kona: %w", err)
}
if c.AsteriscKonaAbsolutePreState == "" && c.AsteriscKonaAbsolutePreStateBaseURL == nil {
return ErrMissingAsteriscKonaAbsolutePreState
}
if c.AsteriscKona.SnapshotFreq == 0 {
return ErrMissingAsteriscKonaSnapshotFreq
}
if c.AsteriscKona.InfoFreq == 0 {
return ErrMissingAsteriscKonaInfoFreq
}
}
if c.TraceTypeEnabled(types.TraceTypeAlphabet) || c.TraceTypeEnabled(types.TraceTypeFast) {
if c.RollupRpc == "" {
return ErrMissingRollupRpc
}
}
if err := c.TxMgrConfig.Check(); err != nil {
return err
}
if err := c.MetricsConfig.Check(); err != nil {
return err
}
if err := c.PprofConfig.Check(); err != nil {
return err
}
return nil
}
func (c Config) validateBaseCannonOptions() error {
if err := c.Cannon.Check(); err != nil {
return fmt.Errorf("cannon: %w", err)
}
if c.CannonAbsolutePreState == "" && c.CannonAbsolutePreStateBaseURL == nil {
return ErrMissingCannonAbsolutePreState
}
if c.Cannon.SnapshotFreq == 0 {
return ErrMissingCannonSnapshotFreq
}
if c.Cannon.InfoFreq == 0 {
return ErrMissingCannonInfoFreq
}
return nil
}
This diff is collapsed.
This diff is collapsed.
package flags
import (
"reflect"
"slices"
"strings"
"testing"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags.
func TestUniqueFlags(t *testing.T) {
seenCLI := make(map[string]struct{})
for _, flag := range Flags {
for _, name := range flag.Names() {
if _, ok := seenCLI[name]; ok {
t.Errorf("duplicate flag %s", name)
continue
}
seenCLI[name] = struct{}{}
}
}
}
// TestUniqueEnvVars asserts that all flag env vars are unique, to avoid accidental conflicts between the many flags.
func TestUniqueEnvVars(t *testing.T) {
seenCLI := make(map[string]struct{})
for _, flag := range Flags {
envVar := envVarForFlag(flag)
if _, ok := seenCLI[envVar]; envVar != "" && ok {
t.Errorf("duplicate flag env var %s", envVar)
continue
}
seenCLI[envVar] = struct{}{}
}
}
func TestCorrectEnvVarPrefix(t *testing.T) {
for _, flag := range Flags {
envVar := envVarForFlag(flag)
if envVar == "" {
t.Errorf("Failed to find EnvVar for flag %v", flag.Names()[0])
}
if !strings.HasPrefix(envVar, "OP_CHALLENGER_") {
t.Errorf("Flag %v env var (%v) does not start with OP_CHALLENGER_", flag.Names()[0], envVar)
}
if strings.Contains(envVar, "__") {
t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.Names()[0], envVar)
}
}
}
func envVarForFlag(flag cli.Flag) string {
values := reflect.ValueOf(flag)
envVarValue := values.Elem().FieldByName("EnvVars")
if envVarValue == (reflect.Value{}) || envVarValue.Len() == 0 {
return ""
}
return envVarValue.Index(0).String()
}
func TestEnvVarFormat(t *testing.T) {
for _, flag := range Flags {
flag := flag
flagName := flag.Names()[0]
skippedFlags := []string{
txmgr.FeeLimitMultiplierFlagName,
txmgr.TxSendTimeoutFlagName,
txmgr.TxNotInMempoolTimeoutFlagName,
}
t.Run(flagName, func(t *testing.T) {
if slices.Contains(skippedFlags, flagName) {
t.Skipf("Skipping flag %v which is known to not have a standard flag name <-> env var conversion", flagName)
}
envFlagGetter, ok := flag.(interface {
GetEnvVars() []string
})
envFlags := envFlagGetter.GetEnvVars()
require.True(t, ok, "must be able to cast the flag to an EnvVar interface")
require.Equal(t, 1, len(envFlags), "flags should have exactly one env var")
expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_CHALLENGER")
require.Equal(t, expectedEnvVar, envFlags[0])
})
}
}
package flags
import (
"fmt"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
opservice "github.com/exchain/go-exchain/op-service"
"github.com/urfave/cli/v2"
)
type FlagCreator func(name string, envVars []string, traceTypeInfo string) cli.Flag
// VMFlag defines a set of flags to set a VM specific option. Provides a flag to set the default plus flags to
// override the default on a per VM basis.
type VMFlag struct {
vms []types.TraceType
name string
envVarPrefix string
flagCreator FlagCreator
}
func NewVMFlag(name string, envVarPrefix string, vms []types.TraceType, flagCreator FlagCreator) *VMFlag {
return &VMFlag{
name: name,
envVarPrefix: envVarPrefix,
flagCreator: flagCreator,
vms: vms,
}
}
func (f *VMFlag) Flags() []cli.Flag {
flags := make([]cli.Flag, 0, len(f.vms))
// Default
defaultEnvVar := opservice.FlagNameToEnvVarName(f.name, f.envVarPrefix)
flags = append(flags, f.flagCreator(f.name, []string{defaultEnvVar}, ""))
for _, vm := range f.vms {
name := f.TraceSpecificFlagName(vm)
envVar := opservice.FlagNameToEnvVarName(name, f.envVarPrefix)
flags = append(flags, f.flagCreator(name, []string{envVar}, fmt.Sprintf("(%v trace type only)", vm)))
}
return flags
}
func (f *VMFlag) DefaultName() string {
return f.name
}
func (f *VMFlag) IsSet(ctx *cli.Context, vm types.TraceType) bool {
return ctx.IsSet(f.TraceSpecificFlagName(vm)) || ctx.IsSet(f.name)
}
func (f *VMFlag) String(ctx *cli.Context, vm types.TraceType) string {
val := ctx.String(f.TraceSpecificFlagName(vm))
if val == "" {
val = ctx.String(f.name)
}
return val
}
func (f *VMFlag) StringSlice(ctx *cli.Context, vm types.TraceType) []string {
val := ctx.StringSlice(f.TraceSpecificFlagName(vm))
if len(val) == 0 {
val = ctx.StringSlice(f.name)
}
return val
}
func (f *VMFlag) SourceFlagName(ctx *cli.Context, vm types.TraceType) string {
vmFlag := f.TraceSpecificFlagName(vm)
if ctx.IsSet(vmFlag) {
return vmFlag
}
return f.name
}
func (f *VMFlag) EitherFlagName(vm types.TraceType) string {
return fmt.Sprintf("%s/%s", f.DefaultName(), f.TraceSpecificFlagName(vm))
}
func (f *VMFlag) TraceSpecificFlagName(vm types.TraceType) string {
return fmt.Sprintf("%v-%v", vm, f.name)
}
package game
import (
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/ethereum/go-ethereum/common"
)
const gameDirPrefix = "game-"
// diskManager coordinates the storage of game data on disk.
type diskManager struct {
datadir string
}
func newDiskManager(dir string) *diskManager {
return &diskManager{datadir: dir}
}
func (d *diskManager) DirForGame(addr common.Address) string {
return filepath.Join(d.datadir, gameDirPrefix+addr.Hex())
}
func (d *diskManager) RemoveAllExcept(keep []common.Address) error {
entries, err := os.ReadDir(d.datadir)
if err != nil {
return fmt.Errorf("failed to list directory: %w", err)
}
var errs []error
for _, entry := range entries {
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), gameDirPrefix) {
// Skip files and directories that don't have the game directory prefix.
// While random content shouldn't be in our datadir, we want to avoid
// deleting things like OS generated files.
continue
}
name := entry.Name()[len(gameDirPrefix):]
addr := common.HexToAddress(name)
if addr == (common.Address{}) {
// Ignore directories with non-address names.
continue
}
if slices.Contains(keep, addr) {
// Preserve data for games we should keep.
continue
}
errs = append(errs, os.RemoveAll(filepath.Join(d.datadir, entry.Name())))
}
return errors.Join(errs...)
}
package game
import (
"os"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestDiskManager_DirForGame(t *testing.T) {
baseDir := t.TempDir()
addr := common.Address{0x53}
disk := newDiskManager(baseDir)
result := disk.DirForGame(addr)
require.Equal(t, filepath.Join(baseDir, gameDirPrefix+addr.Hex()), result)
}
func TestDiskManager_RemoveAllExcept(t *testing.T) {
baseDir := t.TempDir()
keep := common.Address{0x53}
delete := common.Address{0xaa}
disk := newDiskManager(baseDir)
keepDir := disk.DirForGame(keep)
deleteDir := disk.DirForGame(delete)
unexpectedFile := filepath.Join(baseDir, "file.txt")
require.NoError(t, os.WriteFile(unexpectedFile, []byte("test"), 0644))
unexpectedDir := filepath.Join(baseDir, "notagame")
require.NoError(t, os.MkdirAll(unexpectedDir, 0777))
invalidHexDir := filepath.Join(baseDir, gameDirPrefix+"0xNOPE")
require.NoError(t, os.MkdirAll(invalidHexDir, 0777))
populateDir := func(dir string) []string {
require.NoError(t, os.MkdirAll(dir, 0777))
file1 := filepath.Join(dir, "test.txt")
require.NoError(t, os.WriteFile(file1, []byte("foo"), 0644))
nestedDirs := filepath.Join(dir, "subdir", "deep")
require.NoError(t, os.MkdirAll(nestedDirs, 0777))
file2 := filepath.Join(nestedDirs, ".foo.txt")
require.NoError(t, os.WriteFile(file2, []byte("foo"), 0644))
return []string{file1, file2}
}
keepFiles := populateDir(keepDir)
populateDir(deleteDir)
require.NoError(t, disk.RemoveAllExcept([]common.Address{keep}))
require.NoDirExists(t, deleteDir, "should have deleted directory")
for _, file := range keepFiles {
require.FileExists(t, file, "should have kept file for active game")
}
require.FileExists(t, unexpectedFile, "should not delete unexpected file")
require.DirExists(t, unexpectedDir, "should not delete unexpected dir")
require.DirExists(t, invalidHexDir, "should not delete dir with invalid address")
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package contracts
import (
"context"
_ "embed"
"math/big"
"github.com/exchain/go-exchain/op-challenger/game/fault/types"
"github.com/exchain/go-exchain/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
//go:embed abis/FaultDisputeGame-1.1.1.json
var faultDisputeGameAbi111 []byte
type FaultDisputeGameContract111 struct {
FaultDisputeGameContractLatest
}
func (f *FaultDisputeGameContract111) AttackTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodAttack, big.NewInt(int64(parent.ContractIndex)), pivot)
return f.txWithBond(ctx, parent.Position.Attack(), call)
}
func (f *FaultDisputeGameContract111) DefendTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodDefend, big.NewInt(int64(parent.ContractIndex)), pivot)
return f.txWithBond(ctx, parent.Position.Defend(), call)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package metrics
type NoopMetrics struct {
}
func (n *NoopMetrics) StartContractRequest(_ string) EndTimer {
return func() {}
}
var _ ContractMetricer = (*NoopMetrics)(nil)
var NoopContractMetrics = &NoopMetrics{}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment