diff --git a/codecov.yml b/codecov.yml index 537ea5dda7..20dc6ea335 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,6 @@ coverage: ignore: - - "packages/relayer/contracts/**/*" + - "packages/relayer/bindings/**/*" - "packages/relayer/mock/**/*" - "packages/relayer/cmd/**/*" status: diff --git a/go.mod b/go.mod index 6b88e30583..e6ab665cd6 100644 --- a/go.mod +++ b/go.mod @@ -43,11 +43,13 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.6.19 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect @@ -70,7 +72,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/iancoleman/strcase v0.2.0 // indirect @@ -100,15 +102,19 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/rabbitmq/amqp091-go v1.8.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/skeema/knownhosts v1.1.1 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.44.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/net v0.10.0 // indirect diff --git a/go.sum b/go.sum index 8efaa6b12e..e096b2967b 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,12 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/buildkite/terminal-to-html/v3 v3.8.0 h1:S7ImMS8W+2yS/9D4ugrXzB95C4AuNaKcaw/eR/95bFU= github.com/buildkite/terminal-to-html/v3 v3.8.0/go.mod h1:j3XxsnYElte/Bo7Pft+U5eQWWbcx3j51uQ8fo43VrjM= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -197,6 +201,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -404,6 +410,8 @@ github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8u github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -443,12 +451,15 @@ github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -466,6 +477,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.22.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU= @@ -492,6 +505,7 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= diff --git a/packages/relayer/.gitignore b/packages/relayer/.gitignore index 7014ac62fc..4903d05bca 100644 --- a/packages/relayer/.gitignore +++ b/packages/relayer/.gitignore @@ -3,6 +3,11 @@ .l1l2.env .l2l3.env .test.env +.l1indexer.env +.l2indexer.env +.l1processor.env +.l2processor.env + main coverage.txt diff --git a/packages/relayer/.l1indexer.example.env b/packages/relayer/.l1indexer.example.env new file mode 100644 index 0000000000..fa3d5e8b0a --- /dev/null +++ b/packages/relayer/.l1indexer.example.env @@ -0,0 +1,21 @@ +HTTP_PORT=4103 +METRICS_HTTP_PORT=6062 +DATABASE_USER=root +DATABASE_PASSWORD=root +DATABASE_NAME=relayer +DATABASE_HOST=localhost:3306 +DATABASE_MAX_IDLE_CONNS=50 +DATABASE_MAX_OPEN_CONNS=3000 +DATABASE_CONN_MAX_LIFETIME=100000 +QUEUE_USER=guest +QUEUE_PASSWORD=guest +QUEUE_HOST=localhost +QUEUE_PORT=5672 +SRC_BRIDGE_ADDRESS=0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE +DEST_BRIDGE_ADDRESS=0x1000777700000000000000000000000000000004 +SRC_TAIKO_ADDRESS=0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82 +SRC_RPC_URL=wss://l1ws.internal.taiko.xyz +DEST_RPC_URL=wss://ws.internal.taiko.xyz +CORS_ORIGINS=* +NUM_GOROUTINES=50 +BLOCK_BATCH_SIZE=100 \ No newline at end of file diff --git a/packages/relayer/.l1processor.example.env b/packages/relayer/.l1processor.example.env new file mode 100644 index 0000000000..859b666682 --- /dev/null +++ b/packages/relayer/.l1processor.example.env @@ -0,0 +1,27 @@ +PROMETHEUS_HTTP_PORT=6062 +DATABASE_USER=root +DATABASE_PASSWORD=root +DATABASE_NAME=relayer +DATABASE_HOST=localhost:3306 +DATABASE_MAX_IDLE_CONNS=50 +DATABASE_MAX_OPEN_CONNS=3000 +DATABASE_CONN_MAX_LIFETIME=100000 +QUEUE_USER=guest +QUEUE_PASSWORD=guest +QUEUE_HOST=localhost +QUEUE_PORT=5672 +PROCESSOR_PRIVATE_KEY= +DEST_BRIDGE_ADDRESS=0x1000777700000000000000000000000000000004 +SRC_ERC20_VAULT_ADDRESS=0xc6e7DF5E7b4f2A278906862b61205850344D4e7d +DEST_ERC20_VAULT_ADDRESS=0x1000777700000000000000000000000000000002 +DEST_ERC721_VAULT_ADDRESS=0x1000777700000000000000000000000000000008 +DEST_ERC1155_VAULT_ADDRESS=0x1000777700000000000000000000000000000009 +DEST_TAIKO_ADDRESS=0x1000777700000000000000000000000000000001 +SRC_SIGNAL_SERVICE_ADDRESS=0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB +SRC_RPC_URL=wss://l1ws.internal.taiko.xyz +DEST_RPC_URL=wss://ws.internal.taiko.xyz +CONFIRMATIONS_BEFORE_PROCESSING=2 +CORS_ORIGINS=* +NUM_GOROUTINES=50 +BLOCK_BATCH_SIZE=100 +HEADER_SYNC_INTERVAL_IN_SECONDS=2 \ No newline at end of file diff --git a/packages/relayer/ERC1155Vault.json b/packages/relayer/ERC1155Vault.json deleted file mode 100644 index bf5ab4ea8f..0000000000 --- a/packages/relayer/ERC1155Vault.json +++ /dev/null @@ -1,815 +0,0 @@ -[ - { - "inputs": [], - "name": "RESOLVER_DENIED", - "type": "error" - }, - { - "inputs": [], - "name": "RESOLVER_INVALID_ADDR", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - } - ], - "name": "RESOLVER_ZERO_ADDR", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INTERFACE_NOT_SUPPORTED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_AMOUNT", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_FROM", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_SRC_CHAIN_ID", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_TO", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_TOKEN", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_USER", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MAX_TOKEN_PER_TXN_EXCEEDED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MESSAGE_NOT_FAILED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MESSAGE_RELEASED_ALREADY", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_TOKEN_ARRAY_MISMATCH", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "AddressManagerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "ctoken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "btoken", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "ctokenSymbol", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "ctokenName", - "type": "string" - } - ], - "name": "BridgedTokenDeployed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "TokenReceived", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "TokenReleased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "TokenSent", - "type": "event" - }, - { - "inputs": [], - "name": "ERC1155_INTERFACE_ID", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ERC721_INTERFACE_ID", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "addressManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "bridgedToCanonical", - "outputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "canonicalToBridged", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isBridgedToken", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "onERC1155BatchReceived", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "onERC1155Received", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "refundTo", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct IBridge.Message", - "name": "message", - "type": "tuple" - } - ], - "name": "onMessageRecalled", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "internalType": "struct BaseNFTVault.CanonicalNFT", - "name": "ctoken", - "type": "tuple" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "receiveToken", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "address", - "name": "refundTo", - "type": "address" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct BaseNFTVault.BridgeTransferOp", - "name": "opt", - "type": "tuple" - } - ], - "name": "sendToken", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAddressManager", - "type": "address" - } - ], - "name": "setAddressManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/relayer/ERC20Vault.json b/packages/relayer/ERC20Vault.json deleted file mode 100644 index 98ef72350f..0000000000 --- a/packages/relayer/ERC20Vault.json +++ /dev/null @@ -1,684 +0,0 @@ -[ - { - "inputs": [], - "name": "RESOLVER_DENIED", - "type": "error" - }, - { - "inputs": [], - "name": "RESOLVER_INVALID_ADDR", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - } - ], - "name": "RESOLVER_ZERO_ADDR", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_AMOUNT", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_FROM", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_SRC_CHAIN_ID", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_TO", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_TOKEN", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_USER", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MESSAGE_NOT_FAILED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MESSAGE_RELEASED_ALREADY", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "AddressManagerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "ctoken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "btoken", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "ctokenSymbol", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "ctokenName", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "ctokenDecimal", - "type": "uint8" - } - ], - "name": "BridgedTokenDeployed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "TokenReceived", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "TokenReleased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "TokenSent", - "type": "event" - }, - { - "inputs": [], - "name": "addressManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "bridgedToCanonical", - "outputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "canonicalToBridged", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isBridgedToken", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "refundTo", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct IBridge.Message", - "name": "message", - "type": "tuple" - } - ], - "name": "onMessageRecalled", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "internalType": "struct ERC20Vault.CanonicalERC20", - "name": "ctoken", - "type": "tuple" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "receiveToken", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "address", - "name": "refundTo", - "type": "address" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct ERC20Vault.BridgeTransferOp", - "name": "opt", - "type": "tuple" - } - ], - "name": "sendToken", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAddressManager", - "type": "address" - } - ], - "name": "setAddressManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/relayer/ERC721Vault.json b/packages/relayer/ERC721Vault.json deleted file mode 100644 index 461abec176..0000000000 --- a/packages/relayer/ERC721Vault.json +++ /dev/null @@ -1,766 +0,0 @@ -[ - { - "inputs": [], - "name": "RESOLVER_DENIED", - "type": "error" - }, - { - "inputs": [], - "name": "RESOLVER_INVALID_ADDR", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - } - ], - "name": "RESOLVER_ZERO_ADDR", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INTERFACE_NOT_SUPPORTED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_AMOUNT", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_FROM", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_SRC_CHAIN_ID", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_TO", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_TOKEN", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_INVALID_USER", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MAX_TOKEN_PER_TXN_EXCEEDED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MESSAGE_NOT_FAILED", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_MESSAGE_RELEASED_ALREADY", - "type": "error" - }, - { - "inputs": [], - "name": "VAULT_TOKEN_ARRAY_MISMATCH", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "AddressManagerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "ctoken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "btoken", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "ctokenSymbol", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "ctokenName", - "type": "string" - } - ], - "name": "BridgedTokenDeployed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "TokenReceived", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "TokenReleased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "TokenSent", - "type": "event" - }, - { - "inputs": [], - "name": "ERC1155_INTERFACE_ID", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ERC721_INTERFACE_ID", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "addressManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "bridgedToCanonical", - "outputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "canonicalToBridged", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "isBridgedToken", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "onERC721Received", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "refundTo", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct IBridge.Message", - "name": "message", - "type": "tuple" - } - ], - "name": "onMessageRecalled", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "internalType": "struct BaseNFTVault.CanonicalNFT", - "name": "ctoken", - "type": "tuple" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - } - ], - "name": "receiveToken", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "tokenIds", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, - { - "internalType": "address", - "name": "refundTo", - "type": "address" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct BaseNFTVault.BridgeTransferOp", - "name": "opt", - "type": "tuple" - } - ], - "name": "sendToken", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAddressManager", - "type": "address" - } - ], - "name": "setAddressManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/relayer/ICrossChainSync.json b/packages/relayer/ICrossChainSync.json deleted file mode 100644 index 24e1ce2372..0000000000 --- a/packages/relayer/ICrossChainSync.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "srcHeight", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "signalRoot", - "type": "bytes32" - } - ], - "name": "CrossChainSynced", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - } - ], - "name": "getCrossChainBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - } - ], - "name": "getCrossChainSignalRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/packages/relayer/README.md b/packages/relayer/README.md index 4fe88c6948..8481e4e257 100644 --- a/packages/relayer/README.md +++ b/packages/relayer/README.md @@ -5,68 +5,50 @@ A relayer for the Bridge to watch and sync event between Layer 1 and Taiko Layer 2. -## Running the app +## Build the source -run `cp .default.env .env`, and add your own private key as `RELAYER_ECDSA_KEY` in `.env`. You need to be running a MySQL instance, and replace all the `MYSQL_` env vars with yours. +Building the `taiko-client` binary requires a Go compiler. Once installed, run: -Run `go run cmd/main.go --help` to see a list of possible configuration flags, or `go run cmd/main.go` to run with defaults, which will process messages from L1 to L2, and from L2 to L1, and start indexing blocks from 0. - -## Project structure - -### bin - -Executable binary, built it with `go build cmd/main.go {options}`. - -### cli - -Command line interface execution folder, intended to instantiate all app dependencies and start them. - -### cmd - -Entry point to the application. There are possible flag configurations for the app. Run `go run cmd/main.go -h` to see possible options, or `go run cmd/main.go` to run it with sensible defaults. - -### contracts - -Autogenerated smart contract bindings with `abigen`. Use `./abigen.sh` to generate the bindings. - -### encoding - -Encoding helpers for packing abi structs or converting types. - -### indexer - -A block indexing service that watches for events happening in batches. - -### message - -A message processor that can act on a specific event and attempt to process them via `bridge.processMessage` call. - -### migrations - -Contains database migrations. They are created and ran with the `goose` binary. - -Install goose: `go install github.com/pressly/goose/v3/cmd/goose@latest` +```sh +make build +``` -Then: -`cd migrations` +## Configuration -`GOOSE_DRIVER=mysql GOOSE_DBSTRING="username:password@/dbname" goose up` +To run an indexer: +run `cp .l1processor.example.env .l1processor.env`, and replace the variables as needed in `.l1processor.env`. You need to be running a MySQL instance and a RabbitMQ instance, and replace all the `MYSQL_` env vars with yours. -To run down migrations you can use: +RabbitMQ can be installed with `./scripts/install-rabbitmq.sh`. -`GOOSE_DRIVER=mysql GOOSE_DBSTRING="username:password@/dbname" goose down` +## Usage -### mock +Review all available sub-commands: -Mocked structs for testing. +```sh +bin/relayer --help +``` -### proof +Review each sub-command's command line flags: -Proof generator, uses `eth_getProof` call under the hood. +```sh +bin/relayer --help +``` -### repo +## Project structure -Database repositories implementing domain Repository interfaces with a concrete MySQL implementation. +| Path | Description | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `bindings/` | [Go contract bindings](https://geth.ethereum.org/docs/dapp/native-bindings) for Taiko smart contracts, and few related utility functions | +| `cmd/` | Main executable for this project | +| `db/` | Database interfaces and connection methods. | +| `encoding/` | Encoding helper utility functions for interacting with smart contract functions | +| `indexer/` | Indexer sub-command | +| `metrics/` | Metrics related | +| `migrations/` | Database migrations | +| `mock/` | Mocks for testing | +| `proof/` | Merkle proof generation service | +| `queue/` | Queue related interfaces and types, with implementations in subfolders | +| `repo/` | Database repository interaction layer | ## API Doc diff --git a/packages/relayer/TaikoL1.json b/packages/relayer/TaikoL1.json deleted file mode 100644 index 07774fcf17..0000000000 --- a/packages/relayer/TaikoL1.json +++ /dev/null @@ -1,1518 +0,0 @@ -[ - { - "inputs": [], - "name": "L1_ALREADY_PROVEN", - "type": "error" - }, - { - "inputs": [], - "name": "L1_ALREADY_PROVEN", - "type": "error" - }, - { - "inputs": [], - "name": "L1_BLOCK_ID_MISMATCH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_BLOCK_ID_MISMATCH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_BLOCK_ID_MISMATCH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_EVIDENCE_MISMATCH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_EVIDENCE_MISMATCH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_FORK_CHOICE_NOT_FOUND", - "type": "error" - }, - { - "inputs": [], - "name": "L1_FORK_CHOICE_NOT_FOUND", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INSUFFICIENT_TOKEN", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INSUFFICIENT_TOKEN", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_ASSIGNMENT", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_ASSIGNMENT", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_BLOCK_ID", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_BLOCK_ID", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_BLOCK_ID", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_CONFIG", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_CONFIG", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_ETH_DEPOSIT", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_ETH_DEPOSIT", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_EVIDENCE", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_EVIDENCE", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_METADATA", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_METADATA", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_ORACLE_PROVER", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_ORACLE_PROVER", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PARAM", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROOF", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROPOSER", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROPOSER", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROVER", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROVER", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROVER_SIG", - "type": "error" - }, - { - "inputs": [], - "name": "L1_INVALID_PROVER_SIG", - "type": "error" - }, - { - "inputs": [], - "name": "L1_NOT_PROVEABLE", - "type": "error" - }, - { - "inputs": [], - "name": "L1_NOT_PROVEABLE", - "type": "error" - }, - { - "inputs": [], - "name": "L1_SAME_PROOF", - "type": "error" - }, - { - "inputs": [], - "name": "L1_SAME_PROOF", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TOO_MANY_BLOCKS", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TOO_MANY_BLOCKS", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST_HASH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST_HASH", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST_NOT_EXIST", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST_NOT_EXIST", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST_RANGE", - "type": "error" - }, - { - "inputs": [], - "name": "L1_TX_LIST_RANGE", - "type": "error" - }, - { - "inputs": [], - "name": "L1_UNEXPECTED_FORK_CHOICE_ID", - "type": "error" - }, - { - "inputs": [], - "name": "L1_UNEXPECTED_FORK_CHOICE_ID", - "type": "error" - }, - { - "inputs": [], - "name": "RESOLVER_DENIED", - "type": "error" - }, - { - "inputs": [], - "name": "RESOLVER_INVALID_ADDR", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - } - ], - "name": "RESOLVER_ZERO_ADDR", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "AddressManagerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "blockId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "prover", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "reward", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "l1Height", - "type": "uint64" - }, - { - "internalType": "bytes32", - "name": "l1Hash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "mixHash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "txListHash", - "type": "bytes32" - }, - { - "internalType": "uint24", - "name": "txListByteStart", - "type": "uint24" - }, - { - "internalType": "uint24", - "name": "txListByteEnd", - "type": "uint24" - }, - { - "internalType": "uint32", - "name": "gasLimit", - "type": "uint32" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "components": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint96", - "name": "amount", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.EthDeposit[]", - "name": "depositsProcessed", - "type": "tuple[]" - } - ], - "indexed": false, - "internalType": "struct TaikoData.BlockMetadata", - "name": "meta", - "type": "tuple" - } - ], - "name": "BlockProposed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "blockId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "prover", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "reward", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "l1Height", - "type": "uint64" - }, - { - "internalType": "bytes32", - "name": "l1Hash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "mixHash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "txListHash", - "type": "bytes32" - }, - { - "internalType": "uint24", - "name": "txListByteStart", - "type": "uint24" - }, - { - "internalType": "uint24", - "name": "txListByteEnd", - "type": "uint24" - }, - { - "internalType": "uint32", - "name": "gasLimit", - "type": "uint32" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "components": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint96", - "name": "amount", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.EthDeposit[]", - "name": "depositsProcessed", - "type": "tuple[]" - } - ], - "indexed": false, - "internalType": "struct TaikoData.BlockMetadata", - "name": "meta", - "type": "tuple" - } - ], - "name": "BlockProposed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "blockId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "parentHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "signalRoot", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "address", - "name": "prover", - "type": "address" - } - ], - "name": "BlockProven", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "blockId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "parentHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "signalRoot", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "address", - "name": "prover", - "type": "address" - } - ], - "name": "BlockProven", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "blockId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "prover", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "name": "BlockVerified", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "blockId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "prover", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "name": "BlockVerified", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "srcHeight", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "signalRoot", - "type": "bytes32" - } - ], - "name": "CrossChainSynced", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "srcHeight", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "signalRoot", - "type": "bytes32" - } - ], - "name": "CrossChainSynced", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint96", - "name": "amount", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - } - ], - "indexed": false, - "internalType": "struct TaikoData.EthDeposit", - "name": "deposit", - "type": "tuple" - } - ], - "name": "EthDeposited", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint96", - "name": "amount", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - } - ], - "indexed": false, - "internalType": "struct TaikoData.EthDeposit", - "name": "deposit", - "type": "tuple" - } - ], - "name": "EthDeposited", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [], - "name": "addressManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "canDepositEthToL2", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "depositEtherToL2", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "depositTaikoToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - } - ], - "name": "getBlock", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "metaHash", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "prover", - "type": "address" - }, - { - "internalType": "uint64", - "name": "proposedAt", - "type": "uint64" - }, - { - "internalType": "uint16", - "name": "nextForkChoiceId", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "verifiedForkChoiceId", - "type": "uint16" - }, - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - }, - { - "internalType": "uint96", - "name": "proofBond", - "type": "uint96" - }, - { - "internalType": "uint16", - "name": "proofWindow", - "type": "uint16" - } - ], - "internalType": "struct TaikoData.Block", - "name": "blk", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getConfig", - "outputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "relaySignalRoot", - "type": "bool" - }, - { - "internalType": "uint64", - "name": "blockMaxProposals", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "blockRingBufferSize", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "blockMaxVerificationsPerTx", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "blockMaxGasLimit", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "blockFeeBaseGas", - "type": "uint32" - }, - { - "internalType": "uint24", - "name": "blockMaxTxListBytes", - "type": "uint24" - }, - { - "internalType": "uint256", - "name": "blockTxListExpiry", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "proposerRewardPerSecond", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "proposerRewardMax", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "proofRegularCooldown", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "proofOracleCooldown", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "proofWindow", - "type": "uint16" - }, - { - "internalType": "uint96", - "name": "proofBond", - "type": "uint96" - }, - { - "internalType": "bool", - "name": "skipProverAssignmentVerificaiton", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "ethDepositRingBufferSize", - "type": "uint256" - }, - { - "internalType": "uint64", - "name": "ethDepositMinCountPerBlock", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "ethDepositMaxCountPerBlock", - "type": "uint64" - }, - { - "internalType": "uint96", - "name": "ethDepositMinAmount", - "type": "uint96" - }, - { - "internalType": "uint96", - "name": "ethDepositMaxAmount", - "type": "uint96" - }, - { - "internalType": "uint256", - "name": "ethDepositGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "ethDepositMaxFee", - "type": "uint256" - } - ], - "internalType": "struct TaikoData.Config", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - } - ], - "name": "getCrossChainBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - } - ], - "name": "getCrossChainSignalRoot", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - }, - { - "internalType": "bytes32", - "name": "parentHash", - "type": "bytes32" - } - ], - "name": "getForkChoice", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "key", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "signalRoot", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "prover", - "type": "address" - }, - { - "internalType": "uint64", - "name": "provenAt", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.ForkChoice", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getStateVariables", - "outputs": [ - { - "components": [ - { - "internalType": "uint64", - "name": "genesisHeight", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "genesisTimestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "numBlocks", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "lastVerifiedBlockId", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "nextEthDepositToProcess", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "numEthDeposits", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.StateVariables", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "getTaikoTokenBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "id", - "type": "uint16" - } - ], - "name": "getVerifierName", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_addressManager", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "_genesisBlockHash", - "type": "bytes32" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "assignment", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "txList", - "type": "bytes" - } - ], - "name": "proposeBlock", - "outputs": [ - { - "components": [ - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "timestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "l1Height", - "type": "uint64" - }, - { - "internalType": "bytes32", - "name": "l1Hash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "mixHash", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "txListHash", - "type": "bytes32" - }, - { - "internalType": "uint24", - "name": "txListByteStart", - "type": "uint24" - }, - { - "internalType": "uint24", - "name": "txListByteEnd", - "type": "uint24" - }, - { - "internalType": "uint32", - "name": "gasLimit", - "type": "uint32" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "components": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint96", - "name": "amount", - "type": "uint96" - }, - { - "internalType": "uint64", - "name": "id", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.EthDeposit[]", - "name": "depositsProcessed", - "type": "tuple[]" - } - ], - "internalType": "struct TaikoData.BlockMetadata", - "name": "meta", - "type": "tuple" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "blockId", - "type": "uint64" - }, - { - "internalType": "bytes", - "name": "input", - "type": "bytes" - } - ], - "name": "proveBlock", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "addr", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAddressManager", - "type": "address" - } - ], - "name": "setAddressManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "state", - "outputs": [ - { - "components": [ - { - "internalType": "uint64", - "name": "genesisHeight", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "genesisTimestamp", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "numEthDeposits", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "nextEthDepositToProcess", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.SlotA", - "name": "slotA", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint64", - "name": "numBlocks", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "nextEthDepositToProcess", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "lastVerifiedAt", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "lastVerifiedBlockId", - "type": "uint64" - } - ], - "internalType": "struct TaikoData.SlotB", - "name": "slotB", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "maxBlocks", - "type": "uint64" - } - ], - "name": "verifyBlocks", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawTaikoToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" - } -] diff --git a/packages/relayer/TokenVault.json b/packages/relayer/TokenVault.json deleted file mode 100644 index 63b7fcee66..0000000000 --- a/packages/relayer/TokenVault.json +++ /dev/null @@ -1,734 +0,0 @@ -[ - { - "inputs": [], - "name": "RESOLVER_DENIED", - "type": "error" - }, - { - "inputs": [], - "name": "RESOLVER_INVALID_ADDR", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - } - ], - "name": "RESOLVER_ZERO_ADDR", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_CANONICAL_TOKEN_NOT_FOUND", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_AMOUNT", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_OWNER", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_SENDER", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_SRC_CHAIN_ID", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_TO", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_TOKEN", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_INVALID_VALUE", - "type": "error" - }, - { - "inputs": [], - "name": "TOKENVAULT_MESSAGE_NOT_FAILED", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "AddressManagerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "canonicalToken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "bridgedToken", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "canonicalTokenSymbol", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "canonicalTokenName", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "canonicalTokenDecimal", - "type": "uint8" - } - ], - "name": "BridgedERC20Deployed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "ERC20Received", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "ERC20Released", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "ERC20Sent", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "EtherSent", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [], - "name": "addressManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "bridgedAddress", - "type": "address" - } - ], - "name": "bridgedToCanonical", - "outputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "canonicalAddress", - "type": "address" - } - ], - "name": "canonicalToBridged", - "outputs": [ - { - "internalType": "address", - "name": "bridgedAddress", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - } - ], - "name": "init", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - } - ], - "name": "isBridgedToken", - "outputs": [ - { - "internalType": "bool", - "name": "isBridged", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "msgHash", - "type": "bytes32" - } - ], - "name": "messageDeposits", - "outputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "internalType": "struct TokenVault.CanonicalERC20", - "name": "canonicalToken", - "type": "tuple" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "receiveERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "srcChainId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "refundAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "depositValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "callValue", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "processingFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "internalType": "struct IBridge.Message", - "name": "message", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "proof", - "type": "bytes" - } - ], - "name": "releaseERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "name", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "allowZeroAddress", - "type": "bool" - } - ], - "name": "resolve", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "destChainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "processingFee", - "type": "uint256" - }, - { - "internalType": "address", - "name": "refundAddress", - "type": "address" - }, - { - "internalType": "string", - "name": "memo", - "type": "string" - } - ], - "name": "sendERC20", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAddressManager", - "type": "address" - } - ], - "name": "setAddressManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/packages/relayer/contracts/bridge/Bridge.go b/packages/relayer/bindings/bridge/Bridge.go similarity index 100% rename from packages/relayer/contracts/bridge/Bridge.go rename to packages/relayer/bindings/bridge/Bridge.go diff --git a/packages/relayer/contracts/erc1155vault/ERC1155Vault.go b/packages/relayer/bindings/erc1155vault/ERC1155Vault.go similarity index 100% rename from packages/relayer/contracts/erc1155vault/ERC1155Vault.go rename to packages/relayer/bindings/erc1155vault/ERC1155Vault.go diff --git a/packages/relayer/contracts/erc20vault/ERC20Vault.go b/packages/relayer/bindings/erc20vault/ERC20Vault.go similarity index 100% rename from packages/relayer/contracts/erc20vault/ERC20Vault.go rename to packages/relayer/bindings/erc20vault/ERC20Vault.go diff --git a/packages/relayer/contracts/erc721vault/ERC721Vault.go b/packages/relayer/bindings/erc721vault/ERC721Vault.go similarity index 100% rename from packages/relayer/contracts/erc721vault/ERC721Vault.go rename to packages/relayer/bindings/erc721vault/ERC721Vault.go diff --git a/packages/relayer/contracts/icrosschainsync/ICrossChainSync.go b/packages/relayer/bindings/icrosschainsync/ICrossChainSync.go similarity index 100% rename from packages/relayer/contracts/icrosschainsync/ICrossChainSync.go rename to packages/relayer/bindings/icrosschainsync/ICrossChainSync.go diff --git a/packages/relayer/contracts/taikol1/TaikoL1.go b/packages/relayer/bindings/taikol1/TaikoL1.go similarity index 100% rename from packages/relayer/contracts/taikol1/TaikoL1.go rename to packages/relayer/bindings/taikol1/TaikoL1.go diff --git a/packages/relayer/contracts/taikol2/TaikoL2.go b/packages/relayer/bindings/taikol2/TaikoL2.go similarity index 100% rename from packages/relayer/contracts/taikol2/TaikoL2.go rename to packages/relayer/bindings/taikol2/TaikoL2.go diff --git a/packages/relayer/bridge.go b/packages/relayer/bridge.go index 506a6eb282..7f2b40da95 100644 --- a/packages/relayer/bridge.go +++ b/packages/relayer/bridge.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) type Bridge interface { diff --git a/packages/relayer/cli/cli.go b/packages/relayer/cli/cli.go deleted file mode 100644 index 80dbd0e418..0000000000 --- a/packages/relayer/cli/cli.go +++ /dev/null @@ -1,392 +0,0 @@ -package cli - -import ( - "context" - "fmt" - "log" - "os" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" - "github.com/labstack/echo/v4" - - "github.com/ethereum/go-ethereum/ethclient" - "github.com/joho/godotenv" - "github.com/pkg/errors" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/db" - "github.com/taikoxyz/taiko-mono/packages/relayer/http" - "github.com/taikoxyz/taiko-mono/packages/relayer/indexer" - "github.com/taikoxyz/taiko-mono/packages/relayer/repo" - "gorm.io/driver/mysql" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -var ( - envVars = []string{ - "HTTP_PORT", - "L1_BRIDGE_ADDRESS", - "L2_BRIDGE_ADDRESS", - "L2_TAIKO_ADDRESS", - "L1_ERC20_VAULT_ADDRESS", - "L2_ERC20_VAULT_ADDRESS", - "L1_RPC_URL", - "L2_RPC_URL", - "MYSQL_USER", - "MYSQL_DATABASE", - "MYSQL_HOST", - "RELAYER_ECDSA_KEY", - "CONFIRMATIONS_BEFORE_PROCESSING", - "PROMETHEUS_HTTP_PORT", - } - - defaultBlockBatchSize = 2 - defaultNumGoroutines = 10 - defaultSubscriptionBackoff = 600 * time.Second - defaultConfirmations = 15 - defaultHeaderSyncIntervalSeconds int = 60 - defaultConfirmationsTimeoutInSeconds = 900 -) - -func Run( - mode relayer.Mode, - watchMode relayer.WatchMode, - layer relayer.Layer, - httpOnly relayer.HTTPOnly, - profitableOnly relayer.ProfitableOnly, -) { - if err := loadAndValidateEnv(); err != nil { - log.Fatal(err) - } - - db, err := openDBConnection(relayer.DBConnectionOpts{ - Name: os.Getenv("MYSQL_USER"), - Password: os.Getenv("MYSQL_PASSWORD"), - Database: os.Getenv("MYSQL_DATABASE"), - Host: os.Getenv("MYSQL_HOST"), - OpenFunc: func(dsn string) (relayer.DB, error) { - gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), - }) - if err != nil { - return nil, err - } - - return db.New(gormDB), nil - }, - }) - - if err != nil { - log.Fatal(err) - } - - sqlDB, err := db.DB() - if err != nil { - log.Fatal(err) - } - - l1EthClient, err := ethclient.Dial(os.Getenv("L1_RPC_URL")) - if err != nil { - log.Fatal(err) - } - - l2EthClient, err := ethclient.Dial(os.Getenv("L2_RPC_URL")) - if err != nil { - log.Fatal(err) - } - - srv, err := newHTTPServer(db, l1EthClient, l2EthClient) - if err != nil { - log.Fatal(err) - } - - forever := make(chan struct{}) - - go func() { - if err := srv.Start(fmt.Sprintf(":%v", os.Getenv("HTTP_PORT"))); err != nil { - log.Fatal(err) - } - }() - - if !httpOnly { - indexers, closeFunc, err := makeIndexers(layer, db, profitableOnly) - if err != nil { - sqlDB.Close() - log.Fatal(err) - } - - defer sqlDB.Close() - defer closeFunc() - - for _, i := range indexers { - go func(i *indexer.Service) { - if err := i.FilterThenSubscribe(context.Background(), mode, watchMode); err != nil { - log.Fatal(err) - } - }(i) - } - } - - <-forever -} - -func makeIndexers( - layer relayer.Layer, - db relayer.DB, - profitableOnly relayer.ProfitableOnly, -) ([]*indexer.Service, func(), error) { - eventRepository, err := repo.NewEventRepository(db) - if err != nil { - return nil, nil, err - } - - blockRepository, err := repo.NewBlockRepository(db) - if err != nil { - return nil, nil, err - } - - blockBatchSize, err := strconv.Atoi(os.Getenv("BLOCK_BATCH_SIZE")) - if err != nil || blockBatchSize <= 0 { - blockBatchSize = defaultBlockBatchSize - } - - numGoroutines, err := strconv.Atoi(os.Getenv("NUM_GOROUTINES")) - if err != nil || numGoroutines <= 0 { - numGoroutines = defaultNumGoroutines - } - - var subscriptionBackoff time.Duration - - subscriptionBackoffInSeconds, err := strconv.Atoi(os.Getenv("SUBSCRIPTION_BACKOFF_IN_SECONDS")) - if err != nil || subscriptionBackoffInSeconds <= 0 { - subscriptionBackoff = defaultSubscriptionBackoff - } else { - subscriptionBackoff = time.Duration(subscriptionBackoffInSeconds) * time.Second - } - - headerSyncIntervalInSeconds, err := strconv.Atoi(os.Getenv("HEADER_SYNC_INTERVAL_IN_SECONDS")) - if err != nil || headerSyncIntervalInSeconds <= 0 { - headerSyncIntervalInSeconds = defaultHeaderSyncIntervalSeconds - } - - confirmations, err := strconv.Atoi(os.Getenv("CONFIRMATIONS_BEFORE_PROCESSING")) - if err != nil || confirmations <= 0 { - confirmations = defaultConfirmations - } - - confirmationsTimeoutInSeconds, err := strconv.Atoi(os.Getenv("CONFIRMATIONS_TIMEOUT_IN_SECONDS")) - if err != nil || confirmationsTimeoutInSeconds <= 0 { - confirmationsTimeoutInSeconds = defaultConfirmationsTimeoutInSeconds - } - - l1EthClient, err := ethclient.Dial(os.Getenv("L1_RPC_URL")) - if err != nil { - log.Fatal(err) - } - - l2EthClient, err := ethclient.Dial(os.Getenv("L2_RPC_URL")) - if err != nil { - log.Fatal(err) - } - - l1RpcClient, err := rpc.DialContext(context.Background(), os.Getenv("L1_RPC_URL")) - if err != nil { - return nil, nil, err - } - - l2RpcClient, err := rpc.DialContext(context.Background(), os.Getenv("L2_RPC_URL")) - if err != nil { - return nil, nil, err - } - - indexers := make([]*indexer.Service, 0) - - if layer == relayer.L1 || layer == relayer.Both { - l1Indexer, err := indexer.NewService(indexer.NewServiceOpts{ - EventRepo: eventRepository, - BlockRepo: blockRepository, - DestEthClient: l2EthClient, - EthClient: l1EthClient, - RPCClient: l1RpcClient, - DestRPCClient: l2RpcClient, - - ECDSAKey: os.Getenv("RELAYER_ECDSA_KEY"), - BridgeAddress: common.HexToAddress(os.Getenv("L1_BRIDGE_ADDRESS")), - DestBridgeAddress: common.HexToAddress(os.Getenv("L2_BRIDGE_ADDRESS")), - DestTaikoAddress: common.HexToAddress(os.Getenv("L2_TAIKO_ADDRESS")), - SrcTaikoAddress: common.HexToAddress(os.Getenv("L1_TAIKO_ADDRESS")), - SrcSignalServiceAddress: common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_ADDRESS")), - DestERC20VaultAddress: common.HexToAddress(os.Getenv("L2_ERC20_VAULT_ADDRESS")), - DestERC721VaultAddress: common.HexToAddress(os.Getenv("L2_ERC721_VAULT_ADDRESS")), - DestERC1155VaultAddress: common.HexToAddress(os.Getenv("L2_ERC1155_VAULT_ADDRESS")), - BlockBatchSize: uint64(blockBatchSize), - NumGoroutines: numGoroutines, - SubscriptionBackoff: subscriptionBackoff, - Confirmations: uint64(confirmations), - ProfitableOnly: profitableOnly, - HeaderSyncIntervalInSeconds: int64(headerSyncIntervalInSeconds), - ConfirmationsTimeoutInSeconds: int64(confirmationsTimeoutInSeconds), - }) - if err != nil { - log.Fatal(err) - } - - indexers = append(indexers, l1Indexer) - } - - if layer == relayer.L2 || layer == relayer.Both { - l2Indexer, err := indexer.NewService(indexer.NewServiceOpts{ - EventRepo: eventRepository, - BlockRepo: blockRepository, - DestEthClient: l1EthClient, - EthClient: l2EthClient, - RPCClient: l2RpcClient, - DestRPCClient: l1RpcClient, - - ECDSAKey: os.Getenv("RELAYER_ECDSA_KEY"), - BridgeAddress: common.HexToAddress(os.Getenv("L2_BRIDGE_ADDRESS")), - DestBridgeAddress: common.HexToAddress(os.Getenv("L1_BRIDGE_ADDRESS")), - DestTaikoAddress: common.HexToAddress(os.Getenv("L1_TAIKO_ADDRESS")), - SrcSignalServiceAddress: common.HexToAddress(os.Getenv("L2_SIGNAL_SERVICE_ADDRESS")), - DestERC20VaultAddress: common.HexToAddress(os.Getenv("L1_ERC20_VAULT_ADDRESS")), - DestERC721VaultAddress: common.HexToAddress(os.Getenv("L1_ERC721_VAULT_ADDRESS")), - DestERC1155VaultAddress: common.HexToAddress(os.Getenv("L1_ERC1155_VAULT_ADDRESS")), - BlockBatchSize: uint64(blockBatchSize), - NumGoroutines: numGoroutines, - SubscriptionBackoff: subscriptionBackoff, - Confirmations: uint64(confirmations), - ProfitableOnly: profitableOnly, - HeaderSyncIntervalInSeconds: int64(headerSyncIntervalInSeconds), - ConfirmationsTimeoutInSeconds: int64(confirmationsTimeoutInSeconds), - }) - if err != nil { - log.Fatal(err) - } - - indexers = append(indexers, l2Indexer) - } - - closeFunc := func() { - l1EthClient.Close() - l2EthClient.Close() - l1RpcClient.Close() - l2RpcClient.Close() - } - - return indexers, closeFunc, nil -} - -func openDBConnection(opts relayer.DBConnectionOpts) (relayer.DB, error) { - dsn := "" - if opts.Password == "" { - dsn = fmt.Sprintf( - "%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", - opts.Name, - opts.Host, - opts.Database, - ) - } else { - dsn = fmt.Sprintf( - "%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", - opts.Name, - opts.Password, - opts.Host, - opts.Database, - ) - } - - db, err := opts.OpenFunc(dsn) - if err != nil { - return nil, err - } - - sqlDB, err := db.DB() - if err != nil { - return nil, err - } - - var ( - defaultMaxIdleConns = 50 - defaultMaxOpenConns = 200 - defaultConnMaxLifetime = 10 * time.Second - ) - - maxIdleConns, err := strconv.Atoi(os.Getenv("MYSQL_MAX_IDLE_CONNS")) - if err != nil || maxIdleConns <= 0 { - maxIdleConns = defaultMaxIdleConns - } - - maxOpenConns, err := strconv.Atoi(os.Getenv("MYSQL_MAX_OPEN_CONNS")) - if err != nil || maxOpenConns <= 0 { - maxOpenConns = defaultMaxOpenConns - } - - var maxLifetime time.Duration - - connMaxLifetime, err := strconv.Atoi(os.Getenv("MYSQL_CONN_MAX_LIFETIME_IN_MS")) - if err != nil || connMaxLifetime <= 0 { - maxLifetime = defaultConnMaxLifetime - } else { - maxLifetime = time.Duration(connMaxLifetime) - } - - // SetMaxOpenConns sets the maximum number of open connections to the database. - sqlDB.SetMaxOpenConns(maxOpenConns) - - // SetMaxIdleConns sets the maximum number of connections in the idle connection pool. - sqlDB.SetMaxIdleConns(maxIdleConns) - - // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. - sqlDB.SetConnMaxLifetime(maxLifetime) - - return db, nil -} - -func loadAndValidateEnv() error { - _ = godotenv.Load() - - missing := make([]string, 0) - - for _, v := range envVars { - e := os.Getenv(v) - if e == "" { - missing = append(missing, v) - } - } - - if len(missing) == 0 { - return nil - } - - return errors.Errorf("Missing env vars: %v", missing) -} - -func newHTTPServer(db relayer.DB, l1EthClient relayer.EthClient, l2EthClient relayer.EthClient) (*http.Server, error) { - eventRepo, err := repo.NewEventRepository(db) - if err != nil { - return nil, err - } - - blockRepo, err := repo.NewBlockRepository(db) - if err != nil { - return nil, err - } - - srv, err := http.NewServer(http.NewServerOpts{ - EventRepo: eventRepo, - Echo: echo.New(), - CorsOrigins: strings.Split(os.Getenv("CORS_ORIGINS"), ","), - L1EthClient: l1EthClient, - L2EthClient: l2EthClient, - BlockRepo: blockRepo, - }) - if err != nil { - return nil, err - } - - return srv, nil -} diff --git a/packages/relayer/cli/cli_test.go b/packages/relayer/cli/cli_test.go deleted file mode 100644 index 3ba1ee1181..0000000000 --- a/packages/relayer/cli/cli_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package cli - -import ( - "os" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/mock" -) - -var dummyEcdsaKey = "8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f" -var dummyAddress = "0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377" - -func Test_loadAndValidateEnvVars(t *testing.T) { - for _, envVar := range envVars { - os.Setenv(envVar, "valid") - } - - assert.Equal(t, loadAndValidateEnv(), nil) -} - -func Test_loadAndValidateEnvVars_missing(t *testing.T) { - for _, envVar := range envVars { - os.Setenv(envVar, "valid") - } - - for _, envVar := range envVars { - os.Setenv(envVar, "") - - err := loadAndValidateEnv() - - assert.NotEqual(t, err, nil) - assert.Equal(t, true, strings.Contains(err.Error(), envVar)) - os.Setenv(envVar, "valid") - } -} - -func Test_openDBConnection(t *testing.T) { - tests := []struct { - name string - opts relayer.DBConnectionOpts - wantErr error - }{ - { - "success", - relayer.DBConnectionOpts{ - Name: "name", - Password: "password", - Host: "host", - Database: "database", - OpenFunc: func(dsn string) (relayer.DB, error) { - db, cancel, err := testMysql(t) - if err != nil { - return nil, err - } - - defer cancel() - - return db, nil - }, - }, - nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := openDBConnection(tt.opts) - assert.Equal(t, tt.wantErr, err) - }) - } -} - -func Test_makeIndexers(t *testing.T) { - db, cancel, err := testMysql(t) - if err != nil { - t.Fatal(err) - } - - defer cancel() - - dbFunc := func(t *testing.T) relayer.DB { - return db - } - - tests := []struct { - name string - layer relayer.Layer - dbFunc func(*testing.T) relayer.DB - envFunc func() func() - wantIndexers int - wantErr error - }{ - { - "successL1", - relayer.L1, - dbFunc, - func() func() { - os.Setenv("L1_RPC_URL", "https://l1rpc.a1.taiko.xyz") - os.Setenv("L2_RPC_URL", "https://l2rpc.a1.taiko.xyz") - os.Setenv("L1_BRIDGE_ADDRESS", dummyAddress) - os.Setenv("L2_BRIDGE_ADDRESS", dummyAddress) - os.Setenv("L1_TAIKO_ADDRESS", dummyAddress) - os.Setenv("L2_TAIKO_ADDRESS", dummyAddress) - os.Setenv("RELAYER_ECDSA_KEY", dummyEcdsaKey) - - return func() { - os.Setenv("L1_RPC_URL", "") - os.Setenv("L2_RPC_URL", "") - os.Setenv("L1_BRIDGE_ADDRESS", "") - os.Setenv("L2_BRIDGE_ADDRESS", "") - os.Setenv("L1_TAIKO_ADDRESS", "") - os.Setenv("L2_TAIKO_ADDRESS", "") - os.Setenv("RELAYER_ECDSA_KEY", "") - } - }, - 1, - nil, - }, - { - "successL2", - relayer.L2, - dbFunc, - func() func() { - os.Setenv("L1_RPC_URL", "https://l1rpc.a1.taiko.xyz") - os.Setenv("L2_RPC_URL", "https://l2rpc.a1.taiko.xyz") - os.Setenv("L1_BRIDGE_ADDRESS", dummyAddress) - os.Setenv("L2_BRIDGE_ADDRESS", dummyAddress) - os.Setenv("L1_TAIKO_ADDRESS", dummyAddress) - os.Setenv("L2_TAIKO_ADDRESS", dummyAddress) - os.Setenv("RELAYER_ECDSA_KEY", dummyEcdsaKey) - - return func() { - os.Setenv("L1_RPC_URL", "") - os.Setenv("L2_RPC_URL", "") - os.Setenv("L1_BRIDGE_ADDRESS", "") - os.Setenv("L2_BRIDGE_ADDRESS", "") - os.Setenv("L1_TAIKO_ADDRESS", "") - os.Setenv("L2_TAIKO_ADDRESS", "") - os.Setenv("RELAYER_ECDSA_KEY", "") - } - }, - 1, - nil, - }, - { - "successBoth", - relayer.Both, - dbFunc, - func() func() { - os.Setenv("L1_RPC_URL", "https://l1rpc.a1.taiko.xyz") - os.Setenv("L2_RPC_URL", "https://l2rpc.a1.taiko.xyz") - os.Setenv("L1_BRIDGE_ADDRESS", dummyAddress) - os.Setenv("L2_BRIDGE_ADDRESS", dummyAddress) - os.Setenv("L1_TAIKO_ADDRESS", dummyAddress) - os.Setenv("L2_TAIKO_ADDRESS", dummyAddress) - os.Setenv("RELAYER_ECDSA_KEY", dummyEcdsaKey) - - return func() { - os.Setenv("L1_RPC_URL", "") - os.Setenv("L2_RPC_URL", "") - os.Setenv("L1_BRIDGE_ADDRESS", "") - os.Setenv("L2_BRIDGE_ADDRESS", "") - os.Setenv("L1_TAIKO_ADDRESS", "") - os.Setenv("L2_TAIKO_ADDRESS", "") - os.Setenv("RELAYER_ECDSA_KEY", "") - } - }, - 2, - nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - reset := tt.envFunc() - if reset != nil { - defer reset() - } - - indexers, cancel, err := makeIndexers(tt.layer, tt.dbFunc(t), relayer.ProfitableOnly(true)) - if cancel != nil { - defer cancel() - } - - if tt.wantErr != nil { - assert.EqualError(t, tt.wantErr, err.Error()) - } else { - assert.Nil(t, err) - assert.Equal(t, tt.wantIndexers, len(indexers)) - } - }) - } -} - -func Test_newHTTPServer(t *testing.T) { - db, cancel, err := testMysql(t) - if err != nil { - t.Fatal(err) - } - - defer cancel() - - srv, err := newHTTPServer(db, &mock.EthClient{}, &mock.EthClient{}) - assert.Nil(t, err) - assert.NotNil(t, srv) -} - -func Test_newHTTPServer_nilDB(t *testing.T) { - _, err := newHTTPServer(nil, &mock.EthClient{}, &mock.EthClient{}) - assert.NotNil(t, err) -} diff --git a/packages/relayer/cli/containers_test.go b/packages/relayer/cli/containers_test.go deleted file mode 100644 index a0f84ad8f1..0000000000 --- a/packages/relayer/cli/containers_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package cli - -import ( - "context" - "fmt" - "testing" - - "github.com/pressly/goose" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/db" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" - "gorm.io/driver/mysql" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -var ( - dbName = "relayer" - dbUsername = "root" - dbPassword = "password" -) - -func testMysql(t *testing.T) (relayer.DB, func(), error) { - req := testcontainers.ContainerRequest{ - Image: "mysql:latest", - ExposedPorts: []string{"3306/tcp", "33060/tcp"}, - Env: map[string]string{ - "MYSQL_ROOT_PASSWORD": dbPassword, - "MYSQL_DATABASE": dbName, - }, - WaitingFor: wait.ForLog("port: 3306 MySQL Community Server - GPL"), - } - - ctx := context.Background() - - mysqlC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - }) - - if err != nil { - t.Fatal(err) - } - - closeContainer := func() { - err := mysqlC.Terminate(ctx) - if err != nil { - t.Fatal(err) - } - } - - host, _ := mysqlC.Host(ctx) - p, _ := mysqlC.MappedPort(ctx, "3306/tcp") - port := p.Int() - - dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify&parseTime=true&multiStatements=true", - dbUsername, dbPassword, host, port, dbName) - - gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), - }) - if err != nil { - t.Fatal(err) - } - - if err := goose.SetDialect("mysql"); err != nil { - t.Fatal(err) - } - - sqlDB, _ := gormDB.DB() - if err := goose.Up(sqlDB, "../migrations"); err != nil { - t.Fatal(err) - } - - return db.New(gormDB), closeContainer, nil -} diff --git a/packages/relayer/cmd/flags/common.go b/packages/relayer/cmd/flags/common.go new file mode 100644 index 0000000000..b98443d470 --- /dev/null +++ b/packages/relayer/cmd/flags/common.go @@ -0,0 +1,171 @@ +package flags + +import ( + "github.com/urfave/cli/v2" +) + +var ( + commonCategory = "COMMON" + indexerCategory = "INDEXER" + processorCategory = "PROCESSOR" +) + +var ( + DatabaseUsername = &cli.StringFlag{ + Name: "db.username", + Usage: "Database connection username", + Required: true, + Category: commonCategory, + EnvVars: []string{"DATABASE_USER"}, + } + DatabasePassword = &cli.StringFlag{ + Name: "db.password", + Usage: "Database connection password", + Required: true, + Category: commonCategory, + EnvVars: []string{"DATABASE_PASSWORD"}, + } + DatabaseHost = &cli.StringFlag{ + Name: "db.host", + Usage: "Database connection host", + Required: true, + Category: commonCategory, + EnvVars: []string{"DATABASE_HOST"}, + } + DatabaseName = &cli.StringFlag{ + Name: "db.name", + Usage: "Database connection name", + Required: true, + Category: commonCategory, + EnvVars: []string{"DATABASE_NAME"}, + } + QueueUsername = &cli.StringFlag{ + Name: "queue.username", + Usage: "Queue connection username", + Required: true, + Category: commonCategory, + EnvVars: []string{"QUEUE_USER"}, + } + QueuePassword = &cli.StringFlag{ + Name: "queue.password", + Usage: "Queue connection password", + Required: true, + Category: commonCategory, + EnvVars: []string{"QUEUE_PASSWORD"}, + } + QueueHost = &cli.StringFlag{ + Name: "queue.host", + Usage: "Queue connection host", + Required: true, + Category: commonCategory, + EnvVars: []string{"QUEUE_HOST"}, + } + QueuePort = &cli.Uint64Flag{ + Name: "queue.port", + Usage: "Queue connection port", + Required: true, + Category: commonCategory, + EnvVars: []string{"QUEUE_PORT"}, + } + SrcRPCUrl = &cli.StringFlag{ + Name: "srcRpcUrl", + Usage: "RPC URL for the source chain", + Required: true, + Category: commonCategory, + EnvVars: []string{"SRC_RPC_URL"}, + } + DestRPCUrl = &cli.StringFlag{ + Name: "destRpcUrl", + Usage: "RPC URL for the destination chain", + Required: true, + Category: commonCategory, + EnvVars: []string{"DEST_RPC_URL"}, + } + DestBridgeAddress = &cli.StringFlag{ + Name: "destBridgeAddress", + Usage: "Bridge address for the destination chain", + Required: true, + Category: commonCategory, + EnvVars: []string{"DEST_BRIDGE_ADDRESS"}, + } +) + +var ( + DatabaseMaxIdleConns = &cli.Uint64Flag{ + Name: "db.maxIdleConns", + Usage: "Database max idle connections", + Value: 50, + Category: commonCategory, + EnvVars: []string{"DATABASE_MAX_IDLE_CONNS"}, + } + DatabaseMaxOpenConns = &cli.Uint64Flag{ + Name: "db.maxOpenConns", + Usage: "Database max open connections", + Value: 200, + Category: commonCategory, + EnvVars: []string{"DATABASE_MAX_OPEN_CONNS"}, + } + DatabaseConnMaxLifetime = &cli.Uint64Flag{ + Name: "db.connMaxLifetime", + Usage: "Database connection max lifetime in seconds", + Value: 10, + Category: commonCategory, + EnvVars: []string{"DATABASE_CONN_MAX_LIFETIME"}, + } + MetricsHTTPPort = &cli.Uint64Flag{ + Name: "metrics.port", + Usage: "Port to run metrics http server on", + Category: commonCategory, + Value: 6061, + EnvVars: []string{"METRICS_HTTP_PORT"}, + } + ETHClientTimeout = &cli.Uint64Flag{ + Name: "ethClientTimeout", + Usage: "Timeout for eth client and contract binding calls", + Category: commonCategory, + Value: 10, + EnvVars: []string{"ETH_CLIENT_TIMEOUT"}, + } +) + +// optional +var ( + CORSOrigins = &cli.StringFlag{ + Name: "http.corsOrigins", + Usage: "Comma-delinated list of cors origins", + Required: false, + Category: commonCategory, + } +) + +// All common flags. +var CommonFlags = []cli.Flag{ + // required + DatabaseUsername, + DatabasePassword, + DatabaseHost, + DatabaseName, + QueueUsername, + QueuePassword, + QueueHost, + QueuePort, + SrcRPCUrl, + DestRPCUrl, + DestBridgeAddress, + // optional + DatabaseMaxIdleConns, + DatabaseConnMaxLifetime, + DatabaseMaxOpenConns, + MetricsHTTPPort, + ETHClientTimeout, +} + +// MergeFlags merges the given flag slices. +func MergeFlags(groups ...[]cli.Flag) []cli.Flag { + var merged []cli.Flag + for _, group := range groups { + merged = append(merged, group...) + } + + return merged +} diff --git a/packages/relayer/cmd/flags/indexer.go b/packages/relayer/cmd/flags/indexer.go new file mode 100644 index 0000000000..f4c6c1540b --- /dev/null +++ b/packages/relayer/cmd/flags/indexer.go @@ -0,0 +1,83 @@ +package flags + +import ( + "github.com/urfave/cli/v2" +) + +var ( + SrcBridgeAddress = &cli.StringFlag{ + Name: "srcBridgeAddress", + Usage: "Bridge address on the source chain", + Required: true, + Category: indexerCategory, + EnvVars: []string{"SRC_BRIDGE_ADDRESS"}, + } +) + +// optional +var ( + BlockBatchSize = &cli.Uint64Flag{ + Name: "blockBatchSize", + Usage: "Block batch size when iterating through blocks", + Value: 10, + Category: indexerCategory, + EnvVars: []string{"BLOCK_BATCH_SIZE"}, + } + MaxNumGoroutines = &cli.Uint64Flag{ + Name: "maxNumGoroutines", + Usage: "Max number of goroutines to spawn simultaneously when indexing", + Value: 10, + Category: indexerCategory, + EnvVars: []string{"NUM_GOROUTINES"}, + } + SubscriptionBackoff = &cli.Uint64Flag{ + Name: "subscriptionBackoff", + Usage: "Subscription backoff in seconds", + Value: 30, + Category: indexerCategory, + EnvVars: []string{"SUBSCRIPTION_BACKOFF_IN_SECONDS"}, + } + SyncMode = &cli.StringFlag{ + Name: "syncMode", + Usage: "Mode of syncing. Pass in 'sync' to continue, and 'resync' to start from genesis again.", + Value: "sync", + Category: indexerCategory, + EnvVars: []string{"SYNC_MODE"}, + } + WatchMode = &cli.StringFlag{ + Name: "watchMode", + Usage: `Mode of watching the chain. Options are: + filter: only filter the chain, when caught up, exit + subscribe: do not filter the chain, only subscribe to new events + filter-and-subscribe: the default behavior, filter the chain and subscribe when caught up + `, + Value: "filter-and-subscribe", + Category: indexerCategory, + EnvVars: []string{"SYNC_MODE"}, + } + SrcTaikoAddress = &cli.StringFlag{ + Name: "srcTaikoAddress", + Usage: "Taiko address on the source chain, required if L1=>L2, not if L2=>L1", + Category: indexerCategory, + EnvVars: []string{"SRC_TAIKO_ADDRESS"}, + } + HTTPPort = &cli.Uint64Flag{ + Name: "http.port", + Usage: "Port to run http server on", + Category: indexerCategory, + Value: 4102, + EnvVars: []string{"HTTP_PORT"}, + } +) + +var IndexerFlags = MergeFlags(CommonFlags, []cli.Flag{ + SrcBridgeAddress, + // optional + HTTPPort, + SrcTaikoAddress, + BlockBatchSize, + MaxNumGoroutines, + SubscriptionBackoff, + SyncMode, + WatchMode, +}) diff --git a/packages/relayer/cmd/flags/processor.go b/packages/relayer/cmd/flags/processor.go new file mode 100644 index 0000000000..dd4dcdaa81 --- /dev/null +++ b/packages/relayer/cmd/flags/processor.go @@ -0,0 +1,110 @@ +package flags + +import ( + "github.com/urfave/cli/v2" +) + +var ( + ProcessorPrivateKey = &cli.StringFlag{ + Name: "processorPrivateKey", + Usage: "Private key to process messages on the destination chain", + Required: true, + Category: processorCategory, + EnvVars: []string{"PROCESSOR_PRIVATE_KEY"}, + } + SrcSignalServiceAddress = &cli.StringFlag{ + Name: "srcSignalServiceAddress", + Usage: "SignalService address for the source chain", + Required: true, + Category: processorCategory, + EnvVars: []string{"SRC_SIGNAL_SERVICE_ADDRESS"}, + } + DestTaikoAddress = &cli.StringFlag{ + Name: "destTaikoAddress", + Usage: "Taiko address for the destination chain", + Required: true, + Category: processorCategory, + EnvVars: []string{"DEST_TAIKO_ADDRESS"}, + } + DestERC20VaultAddress = &cli.StringFlag{ + Name: "destERC20VaultAddress", + Usage: "ERC20Vault address for the destination chain, only required if you want to process NFTs", + Category: processorCategory, + Required: true, + EnvVars: []string{"DEST_ERC20_VAULT_ADDRESS"}, + } + DestERC1155VaultAddress = &cli.StringFlag{ + Name: "destERC1155Address", + Usage: "ERC1155Vault address for the destination chain", + Category: processorCategory, + Required: true, + EnvVars: []string{"DEST_ERC1155_VAULT_ADDRESS"}, + } + DestERC721VaultAddress = &cli.StringFlag{ + Name: "destERC721Address", + Usage: "ERC721Vault address for the destination chain", + Category: processorCategory, + Required: true, + EnvVars: []string{"DEST_ERC721_VAULT_ADDRESS"}, + } +) + +// optional +var ( + HeaderSyncInterval = &cli.Uint64Flag{ + Name: "headerSyncInterval", + Usage: "Interval to poll to see if header is synced yet, in seconds", + Value: 10, + Category: processorCategory, + EnvVars: []string{"HEADER_SYNC_INTERVAL_IN_SECONDS"}, + } + Confirmations = &cli.Uint64Flag{ + Name: "confirmations", + Usage: "Confirmations to wait for on source chain before processing on destination chain", + Value: 3, + Category: processorCategory, + EnvVars: []string{"CONFIRMATIONS_BEFORE_PROCESSING"}, + } + ConfirmationTimeout = &cli.Uint64Flag{ + Name: "confirmationTimeout", + Usage: "Timeout when waiting for a processed message receipt in seconds", + Value: 360, + Category: processorCategory, + EnvVars: []string{"CONFIRMATIONS_TIMEOUT_IN_SECONDS"}, + } + ProfitableOnly = &cli.BoolFlag{ + Name: "profitableOnly", + Usage: "Whether to only process transactions that are estimated to be profitable", + Value: false, + Category: processorCategory, + EnvVars: []string{"PROFITABLE_ONLY"}, + } + BackOffRetryInterval = &cli.Uint64Flag{ + Name: "backoff.retryInterval", + Usage: "Retry interval in seconds when there is an error", + Category: processorCategory, + Value: 12, + } + BackOffMaxRetrys = &cli.Uint64Flag{ + Name: "backoff.maxRetrys", + Usage: "Max retry times when there is an error", + Category: processorCategory, + Value: 3, + } +) + +var ProcessorFlags = MergeFlags(CommonFlags, []cli.Flag{ + SrcSignalServiceAddress, + DestERC721VaultAddress, + DestERC1155VaultAddress, + DestERC20VaultAddress, + DestTaikoAddress, + ProcessorPrivateKey, + // optional + HeaderSyncInterval, + Confirmations, + ConfirmationTimeout, + ProfitableOnly, + BackOffRetryInterval, + BackOffMaxRetrys, +}) diff --git a/packages/relayer/cmd/main.go b/packages/relayer/cmd/main.go index e83c3888da..adb674a3f7 100644 --- a/packages/relayer/cmd/main.go +++ b/packages/relayer/cmd/main.go @@ -1,62 +1,59 @@ package main import ( - "flag" + "fmt" "log" - - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/cli" + "os" + + "github.com/joho/godotenv" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/utils" + "github.com/taikoxyz/taiko-mono/packages/relayer/indexer" + "github.com/taikoxyz/taiko-mono/packages/relayer/processor" + "github.com/urfave/cli/v2" ) func main() { - modePtr := flag.String("mode", string(relayer.SyncMode), `mode to run in. - options: - sync: continue syncing from previous block - resync: restart syncing from block 0 - fromBlock: restart syncing from specified block number - `) - - layersPtr := flag.String("layers", string(relayer.Both), `layers to watch and process. - options: - l1: only watch l1 => l2 bridge messages - l2: only watch l2 => l1 bridge messages - both: watch l1 => l2 and l2 => l1 bridge messages - `) - - watchModePtr := flag.String("watch-mode", string(relayer.FilterAndSubscribeWatchMode), `watch mode to run in. - options: - filter: only filter previous messages - subscribe: only subscribe to new messages - filter-and-subscribe: catch up on all previous messages, then subscribe to new messages - `) - - httpOnlyPtr := flag.Bool("http-only", false, `only run an http server and don't index blocks. - options: - true: only run an http server, dont index blocks - false: run an http server and index blocks - `) - - profitableOnlyPtr := flag.Bool("profitable-only", false, `only process profitable transactions. - options: - true: - false: - `) - - flag.Parse() - - if !relayer.IsInSlice(relayer.Mode(*modePtr), relayer.Modes) { - log.Fatal("mode not valid") + app := cli.NewApp() + + log.SetOutput(os.Stdout) + // attempt to load a .env file to overwrite CLI flags, but allow it to not + // exist. + + envFile := os.Getenv("RELAYER_ENV_FILE") + if envFile == "" { + envFile = ".env" } - if !relayer.IsInSlice(relayer.Layer(*layersPtr), relayer.Layers) { - log.Fatal("mode not valid") + _ = godotenv.Load(envFile) + + app.Name = "Taiko Relayer" + app.Usage = "The taiko relayer softwares command line interface" + app.Copyright = "Copyright 2021-2023 Taiko Labs" + app.Description = "Bridge relayer implementation in Golang for Taiko protocol" + app.Authors = []*cli.Author{{Name: "Taiko Labs", Email: "info@taiko.xyz"}} + app.EnableBashCompletion = true + + // All supported sub commands. + app.Commands = []*cli.Command{ + { + Name: "indexer", + Flags: flags.IndexerFlags, + Usage: "Starts the indexer software", + Description: "Taiko relayer indexer software", + Action: utils.SubcommandAction(new(indexer.Indexer)), + }, + { + Name: "processor", + Flags: flags.ProcessorFlags, + Usage: "Starts the processor software", + Description: "Taiko relayer processor software", + Action: utils.SubcommandAction(new(processor.Processor)), + }, } - cli.Run( - relayer.Mode(*modePtr), - relayer.WatchMode(*watchModePtr), - relayer.Layer(*layersPtr), - relayer.HTTPOnly(*httpOnlyPtr), - relayer.ProfitableOnly(*profitableOnlyPtr), - ) + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } } diff --git a/packages/relayer/cmd/utils/subcommand.go b/packages/relayer/cmd/utils/subcommand.go new file mode 100644 index 0000000000..b4fa237b77 --- /dev/null +++ b/packages/relayer/cmd/utils/subcommand.go @@ -0,0 +1,62 @@ +package utils + +import ( + "context" + "os" + "os/signal" + "syscall" + + "log/slog" + + "github.com/taikoxyz/taiko-mono/packages/relayer/metrics" + "github.com/urfave/cli/v2" +) + +type SubcommandApplication interface { + InitFromCli(context.Context, *cli.Context) error + Name() string + Start() error + Close(context.Context) +} + +func SubcommandAction(app SubcommandApplication) cli.ActionFunc { + return func(c *cli.Context) error { + ctx, ctxClose := context.WithCancel(context.Background()) + defer func() { ctxClose() }() + + if err := app.InitFromCli(ctx, c); err != nil { + return err + } + + slog.Info("Starting Taiko relayer application", "name", app.Name()) + + if err := app.Start(); err != nil { + slog.Error("Starting application error", "name", app.Name(), "error", err) + return err + } + + _, startMetrics := metrics.Serve(ctx, c) + + if err := startMetrics(); err != nil { + slog.Error("Starting metrics server error", "error", err) + return err + } + + defer func() { + ctxClose() + app.Close(ctx) + slog.Info("Application stopped", "name", app.Name()) + }() + + quitCh := make(chan os.Signal, 1) + signal.Notify(quitCh, []os.Signal{ + os.Interrupt, + os.Kill, + syscall.SIGTERM, + syscall.SIGQUIT, + }...) + <-quitCh + + return nil + } +} diff --git a/packages/relayer/db.go b/packages/relayer/db.go index fe1e4fde35..02798e0e23 100644 --- a/packages/relayer/db.go +++ b/packages/relayer/db.go @@ -12,11 +12,14 @@ var ( ) type DBConnectionOpts struct { - Name string - Password string - Host string - Database string - OpenFunc func(dsn string) (DB, error) + Name string + Password string + Host string + Database string + MaxIdleConns uint64 + MaxOpenConns uint64 + MaxConnLifetime uint64 + OpenFunc func(dsn string) (DB, error) } type DB interface { diff --git a/packages/relayer/db/db.go b/packages/relayer/db/db.go index 4f557011cf..9223263b3c 100644 --- a/packages/relayer/db/db.go +++ b/packages/relayer/db/db.go @@ -2,7 +2,10 @@ package db import ( "database/sql" + "fmt" + "time" + "github.com/cyberhorsey/errors" "gorm.io/gorm" ) @@ -23,3 +26,59 @@ func New(gormdb *gorm.DB) *DB { gormdb: gormdb, } } + +var ( + ErrNoDB = errors.Validation.NewWithKeyAndDetail("ERR_NO_DB", "DB is required") +) + +type DBConnectionOpts struct { + Name string + Password string + Host string + Database string + MaxIdleConns uint64 + MaxOpenConns uint64 + MaxConnLifetime uint64 + OpenFunc func(dsn string) (*DB, error) +} + +func OpenDBConnection(opts DBConnectionOpts) (*DB, error) { + dsn := "" + if opts.Password == "" { + dsn = fmt.Sprintf( + "%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", + opts.Name, + opts.Host, + opts.Database, + ) + } else { + dsn = fmt.Sprintf( + "%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", + opts.Name, + opts.Password, + opts.Host, + opts.Database, + ) + } + + db, err := opts.OpenFunc(dsn) + if err != nil { + return nil, err + } + + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + + // SetMaxOpenConns sets the maximum number of open connections to the database. + sqlDB.SetMaxOpenConns(int(opts.MaxOpenConns)) + + // SetMaxIdleConns sets the maximum number of connections in the idle connection pool. + sqlDB.SetMaxIdleConns(int(opts.MaxIdleConns)) + + // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. + sqlDB.SetConnMaxLifetime(time.Duration(opts.MaxConnLifetime)) + + return db, nil +} diff --git a/packages/relayer/flags.go b/packages/relayer/flags.go deleted file mode 100644 index 59cebcb111..0000000000 --- a/packages/relayer/flags.go +++ /dev/null @@ -1,31 +0,0 @@ -package relayer - -type Mode string - -var ( - SyncMode Mode = "sync" - ResyncMode Mode = "resync" - Modes = []Mode{SyncMode, ResyncMode} -) - -type Layer string - -var ( - L1 Layer = "l1" - L2 Layer = "l2" - Both Layer = "both" - Layers = []Layer{L1, L2, Both} -) - -type WatchMode string - -var ( - FilterWatchMode WatchMode = "filter" - SubscribeWatchMode WatchMode = "subscribe" - FilterAndSubscribeWatchMode WatchMode = "filter-and-subscribe" - WatchModes = []WatchMode{FilterWatchMode, SubscribeWatchMode} -) - -type HTTPOnly bool - -type ProfitableOnly bool diff --git a/packages/relayer/indexer/config.go b/packages/relayer/indexer/config.go new file mode 100644 index 0000000000..95add0a934 --- /dev/null +++ b/packages/relayer/indexer/config.go @@ -0,0 +1,114 @@ +package indexer + +import ( + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/taikoxyz/taiko-mono/packages/relayer/db" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue/rabbitmq" + "github.com/urfave/cli/v2" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +type Config struct { + // address configs + SrcBridgeAddress common.Address + SrcTaikoAddress common.Address + DestBridgeAddress common.Address + // db configs + DatabaseUsername string + DatabasePassword string + DatabaseName string + DatabaseHost string + DatabaseMaxIdleConns uint64 + DatabaseMaxOpenConns uint64 + DatabaseMaxConnLifetime uint64 + // queue configs + QueueUsername string + QueuePassword string + QueueHost string + QueuePort uint64 + // rpc configs + SrcRPCUrl string + DestRPCUrl string + ETHClientTimeout uint64 + CORSOrigins []string + BlockBatchSize uint64 + NumGoroutines uint64 + SubscriptionBackoff uint64 + SyncMode SyncMode + WatchMode WatchMode + HTTPPort uint64 + OpenQueueFunc func() (queue.Queue, error) + OpenDBFunc func() (DB, error) +} + +// NewConfigFromCliContext creates a new config instance from command line flags. +func NewConfigFromCliContext(c *cli.Context) (*Config, error) { + return &Config{ + SrcBridgeAddress: common.HexToAddress(c.String(flags.SrcBridgeAddress.Name)), + SrcTaikoAddress: common.HexToAddress(c.String(flags.SrcTaikoAddress.Name)), + DestBridgeAddress: common.HexToAddress(c.String(flags.DestBridgeAddress.Name)), + DatabaseUsername: c.String(flags.DatabaseUsername.Name), + DatabasePassword: c.String(flags.DatabasePassword.Name), + DatabaseName: c.String(flags.DatabaseName.Name), + DatabaseHost: c.String(flags.DatabaseHost.Name), + DatabaseMaxIdleConns: c.Uint64(flags.DatabaseMaxIdleConns.Name), + DatabaseMaxOpenConns: c.Uint64(flags.DatabaseMaxOpenConns.Name), + DatabaseMaxConnLifetime: c.Uint64(flags.DatabaseConnMaxLifetime.Name), + QueueUsername: c.String(flags.QueueUsername.Name), + QueuePassword: c.String(flags.QueuePassword.Name), + QueuePort: c.Uint64(flags.QueuePort.Name), + QueueHost: c.String(flags.QueueHost.Name), + SrcRPCUrl: c.String(flags.SrcRPCUrl.Name), + DestRPCUrl: c.String(flags.DestRPCUrl.Name), + CORSOrigins: strings.Split(c.String(flags.CORSOrigins.Name), ","), + BlockBatchSize: c.Uint64(flags.BlockBatchSize.Name), + NumGoroutines: c.Uint64(flags.MaxNumGoroutines.Name), + SubscriptionBackoff: c.Uint64(flags.SubscriptionBackoff.Name), + WatchMode: WatchMode(c.String(flags.WatchMode.Name)), + SyncMode: SyncMode(c.String(flags.SyncMode.Name)), + HTTPPort: c.Uint64(flags.HTTPPort.Name), + ETHClientTimeout: c.Uint64(flags.ETHClientTimeout.Name), + OpenDBFunc: func() (DB, error) { + return db.OpenDBConnection(db.DBConnectionOpts{ + Name: c.String(flags.DatabaseUsername.Name), + Password: c.String(flags.DatabasePassword.Name), + Database: c.String(flags.DatabaseName.Name), + Host: c.String(flags.DatabaseHost.Name), + MaxIdleConns: c.Uint64(flags.DatabaseMaxIdleConns.Name), + MaxOpenConns: c.Uint64(flags.DatabaseMaxOpenConns.Name), + MaxConnLifetime: c.Uint64(flags.DatabaseConnMaxLifetime.Name), + OpenFunc: func(dsn string) (*db.DB, error) { + gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + return nil, err + } + + return db.New(gormDB), nil + }, + }) + }, + OpenQueueFunc: func() (queue.Queue, error) { + opts := queue.NewQueueOpts{ + Username: c.String(flags.QueueUsername.Name), + Password: c.String(flags.QueuePassword.Name), + Host: c.String(flags.QueueHost.Name), + Port: c.String(flags.QueuePort.Name), + } + + q, err := rabbitmq.NewQueue(opts) + if err != nil { + return nil, err + } + + return q, nil + }, + }, nil +} diff --git a/packages/relayer/indexer/config_test.go b/packages/relayer/indexer/config_test.go new file mode 100644 index 0000000000..610e2bce51 --- /dev/null +++ b/packages/relayer/indexer/config_test.go @@ -0,0 +1,80 @@ +package indexer + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/taikoxyz/taiko-mono/packages/relayer/mock" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" + "github.com/urfave/cli/v2" +) + +var ( + srcTaikoAddr = "0x53FaC9201494f0bd17B9892B9fae4d52fe3BD377" + srcBridgeAddr = "0x73FaC9201494f0bd17B9892B9fae4d52fe3BD377" + destBridgeAddr = "0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377" +) + +func setupApp() *cli.App { + app := cli.NewApp() + app.Flags = flags.IndexerFlags + app.Action = func(ctx *cli.Context) error { + _, err := NewConfigFromCliContext(ctx) + return err + } + + return app +} + +func TestNewConfigFromCliContext(t *testing.T) { + app := setupApp() + + app.Action = func(ctx *cli.Context) error { + c, err := NewConfigFromCliContext(ctx) + assert.Nil(t, err) + assert.Equal(t, "dbuser", c.DatabaseUsername) + assert.Equal(t, "dbpass", c.DatabasePassword) + assert.Equal(t, "dbname", c.DatabaseName) + assert.Equal(t, "dbhost", c.DatabaseHost) + assert.Equal(t, "queuename", c.QueueUsername) + assert.Equal(t, "queuepassword", c.QueuePassword) + assert.Equal(t, "queuehost", c.QueueHost) + assert.Equal(t, uint64(5555), c.QueuePort) + assert.Equal(t, "srcRpcUrl", c.SrcRPCUrl) + assert.Equal(t, "destRpcUrl", c.DestRPCUrl) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.DestBridgeAddress) + assert.Equal(t, common.HexToAddress(srcBridgeAddr), c.SrcBridgeAddress) + assert.Equal(t, common.HexToAddress(srcTaikoAddr), c.SrcTaikoAddress) + + c.OpenDBFunc = func() (DB, error) { + return &mock.DB{}, nil + } + + c.OpenQueueFunc = func() (queue.Queue, error) { + return &mock.Queue{}, nil + } + + // assert.Nil(t, InitFromConfig(context.Background(), new(Indexer), c)) + + return err + } + + assert.Nil(t, app.Run([]string{ + "TestNewConfigFromCliContext", + "-" + flags.DatabaseUsername.Name, "dbuser", + "-" + flags.DatabasePassword.Name, "dbpass", + "-" + flags.DatabaseHost.Name, "dbhost", + "-" + flags.DatabaseName.Name, "dbname", + "-" + flags.QueueUsername.Name, "queuename", + "-" + flags.QueuePassword.Name, "queuepassword", + "-" + flags.QueueHost.Name, "queuehost", + "-" + flags.QueuePort.Name, "5555", + "-" + flags.SrcRPCUrl.Name, "srcRpcUrl", + "-" + flags.DestRPCUrl.Name, "destRpcUrl", + "-" + flags.DestBridgeAddress.Name, destBridgeAddr, + "-" + flags.SrcBridgeAddress.Name, srcBridgeAddr, + "-" + flags.SrcTaikoAddress.Name, srcTaikoAddr, + })) +} diff --git a/packages/relayer/indexer/detect_and_handle_reorg.go b/packages/relayer/indexer/detect_and_handle_reorg.go index 6ad827c303..c82c75f1d9 100644 --- a/packages/relayer/indexer/detect_and_handle_reorg.go +++ b/packages/relayer/indexer/detect_and_handle_reorg.go @@ -8,8 +8,8 @@ import ( "log/slog" ) -func (svc *Service) detectAndHandleReorg(ctx context.Context, eventType string, msgHash string) error { - e, err := svc.eventRepo.FirstByEventAndMsgHash(ctx, eventType, msgHash) +func (i *Indexer) detectAndHandleReorg(ctx context.Context, eventType string, msgHash string) error { + e, err := i.eventRepo.FirstByEventAndMsgHash(ctx, eventType, msgHash) if err != nil { return errors.Wrap(err, "svc.eventRepo.FirstByMsgHash") } @@ -21,7 +21,7 @@ func (svc *Service) detectAndHandleReorg(ctx context.Context, eventType string, // reorg detected slog.Info("reorg detected", "msgHash", msgHash, "eventType", eventType) - err = svc.eventRepo.Delete(ctx, e.ID) + err = i.eventRepo.Delete(ctx, e.ID) if err != nil { return errors.Wrap(err, "svc.eventRepo.Delete") } diff --git a/packages/relayer/indexer/filter_then_subscribe.go b/packages/relayer/indexer/filter_then_subscribe.go deleted file mode 100644 index 2292bfc75d..0000000000 --- a/packages/relayer/indexer/filter_then_subscribe.go +++ /dev/null @@ -1,166 +0,0 @@ -package indexer - -import ( - "context" - "fmt" - - "log/slog" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/labstack/gommon/log" - "github.com/pkg/errors" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "golang.org/x/sync/errgroup" -) - -var ( - eventName = relayer.EventNameMessageSent -) - -// FilterThenSubscribe gets the most recent block height that has been indexed, and works it's way -// up to the latest block. As it goes, it tries to process messages. -// When it catches up, it then starts to Subscribe to latest events as they come in. -func (svc *Service) FilterThenSubscribe( - ctx context.Context, - mode relayer.Mode, - watchMode relayer.WatchMode, -) error { - chainID, err := svc.ethClient.ChainID(ctx) - if err != nil { - return errors.Wrap(err, "svc.ethClient.ChainID()") - } - - go scanBlocks(ctx, svc.ethClient, chainID) - - // if subscribing to new events, skip filtering and subscribe - if watchMode == relayer.SubscribeWatchMode { - return svc.subscribe(ctx, chainID) - } - - if err := svc.setInitialProcessingBlockByMode(ctx, mode, chainID); err != nil { - return errors.Wrap(err, "svc.setInitialProcessingBlockByMode") - } - - header, err := svc.ethClient.HeaderByNumber(ctx, nil) - if err != nil { - return errors.Wrap(err, "svc.ethClient.HeaderByNumber") - } - - if svc.processingBlockHeight == header.Number.Uint64() { - slog.Info("indexing caught up, subscribing to new incoming events", "chainID", chainID.Uint64()) - return svc.subscribe(ctx, chainID) - } - - slog.Info("fetching batch block events", - "chainID", chainID.Uint64(), - "startblock", svc.processingBlockHeight, - "endblock", header.Number.Int64(), - "batchsize", svc.blockBatchSize, - ) - - for i := svc.processingBlockHeight; i < header.Number.Uint64(); i += svc.blockBatchSize { - end := svc.processingBlockHeight + svc.blockBatchSize - // if the end of the batch is greater than the latest block number, set end - // to the latest block number - if end > header.Number.Uint64() { - end = header.Number.Uint64() - } - - // filter exclusive of the end block. - // we use "end" as the next starting point of the batch, and - // process up to end - 1 for this batch. - filterEnd := end - 1 - - fmt.Printf("block batch from %v to %v", i, filterEnd) - fmt.Println() - - filterOpts := &bind.FilterOpts{ - Start: svc.processingBlockHeight, - End: &filterEnd, - Context: ctx, - } - - messageStatusChangedEvents, err := svc.bridge.FilterMessageStatusChanged(filterOpts, nil) - if err != nil { - return errors.Wrap(err, "bridge.FilterMessageStatusChanged") - } - - // we dont need to do anything with msgStatus events except save them to the DB. - // we dont need to process them. they are for exposing via the API. - - err = svc.saveMessageStatusChangedEvents(ctx, chainID, messageStatusChangedEvents) - if err != nil { - return errors.Wrap(err, "bridge.saveMessageStatusChangedEvents") - } - - messageSentEvents, err := svc.bridge.FilterMessageSent(filterOpts, nil) - if err != nil { - return errors.Wrap(err, "bridge.FilterMessageSent") - } - - if !messageSentEvents.Next() || messageSentEvents.Event == nil { - // use "end" not "filterEnd" here, because it will be used as the start - // of the next batch. - if err := svc.handleNoEventsInBatch(ctx, chainID, int64(end)); err != nil { - return errors.Wrap(err, "svc.handleNoEventsInBatch") - } - - continue - } - - group, groupCtx := errgroup.WithContext(ctx) - - group.SetLimit(svc.numGoroutines) - - for { - event := messageSentEvents.Event - - group.Go(func() error { - err := svc.handleEvent(groupCtx, chainID, event) - if err != nil { - relayer.ErrorEvents.Inc() - // log error but always return nil to keep other goroutines active - log.Error(err.Error()) - } - - return nil - }) - - // if there are no more events - if !messageSentEvents.Next() { - // wait for the last of the goroutines to finish - if err := group.Wait(); err != nil { - return errors.Wrap(err, "group.Wait") - } - // handle no events remaining, saving the processing block and restarting the for - // loop - if err := svc.handleNoEventsInBatch(ctx, chainID, int64(end)); err != nil { - return errors.Wrap(err, "svc.handleNoEventsInBatch") - } - - break - } - } - } - - log.Infof( - "chain id %v indexer fully caught up, checking latest block number to see if it's advanced", - chainID.Uint64(), - ) - - latestBlock, err := svc.ethClient.HeaderByNumber(ctx, nil) - if err != nil { - return errors.Wrap(err, "svc.ethclient.HeaderByNumber") - } - - if svc.processingBlockHeight < latestBlock.Number.Uint64() { - return svc.FilterThenSubscribe(ctx, relayer.SyncMode, watchMode) - } - - // we are caught up and specified not to subscribe, we can return now - if watchMode == relayer.FilterWatchMode { - return nil - } - - return svc.subscribe(ctx, chainID) -} diff --git a/packages/relayer/indexer/handle_event.go b/packages/relayer/indexer/handle_event.go index 24fee16aad..d5f580ac47 100644 --- a/packages/relayer/indexer/handle_event.go +++ b/packages/relayer/indexer/handle_event.go @@ -7,21 +7,23 @@ import ( "log/slog" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" ) // handleEvent handles an individual MessageSent event -func (svc *Service) handleEvent( +func (i *Indexer) handleEvent( ctx context.Context, chainID *big.Int, event *bridge.BridgeMessageSent, ) error { slog.Info("event found for msgHash", "msgHash", common.Hash(event.MsgHash).Hex(), "txHash", event.Raw.TxHash.Hex()) - if err := svc.detectAndHandleReorg(ctx, relayer.EventNameMessageSent, common.Hash(event.MsgHash).Hex()); err != nil { + if err := i.detectAndHandleReorg(ctx, relayer.EventNameMessageSent, common.Hash(event.MsgHash).Hex()); err != nil { return errors.Wrap(err, "svc.detectAndHandleReorg") } @@ -30,7 +32,7 @@ func (svc *Service) handleEvent( return nil } - eventStatus, err := svc.eventStatusFromMsgHash(ctx, event.Message.GasLimit, event.MsgHash) + eventStatus, err := i.eventStatusFromMsgHash(ctx, event.Message.GasLimit, event.MsgHash) if err != nil { return errors.Wrap(err, "svc.eventStatusFromMsgHash") } @@ -64,55 +66,43 @@ func (svc *Service) handleEvent( opts.CanonicalTokenDecimals = canonicalToken.TokenDecimals() } - e, err := svc.eventRepo.Save(ctx, opts) + e, err := i.eventRepo.Save(ctx, opts) if err != nil { return errors.Wrap(err, "svc.eventRepo.Save") } - if !canProcessMessage(ctx, eventStatus, event.Message.User, svc.relayerAddr) { - slog.Warn("cant process message", "msgHash", common.Hash(event.MsgHash).Hex(), "eventStatus", eventStatus) - return nil - } - - // process the message - if err := svc.processor.ProcessMessage(ctx, event, e); err != nil { - return errors.Wrap(err, "svc.processMessage") + // TODO: add to queue + msg := queue.QueueMessageBody{ + ID: e.ID, + Event: event, } - return nil -} - -func canProcessMessage( - ctx context.Context, - eventStatus relayer.EventStatus, - messageOwner common.Address, - relayerAddress common.Address, -) bool { - // we can not process, exit early - if eventStatus == relayer.EventStatusNewOnlyOwner { - if messageOwner != relayerAddress { - slog.Info("gasLimit == 0 and owner is not the current relayer key, can not process. continuing loop") - return false - } - - return true + marshalledMsg, err := json.Marshal(msg) + if err != nil { + return errors.Wrap(err, "json.Marshal") } - if eventStatus == relayer.EventStatusNew { - return true + if err := i.queue.Publish(ctx, marshalledMsg); err != nil { + return errors.Wrap(err, "i.queue.Publish") } - return false + return nil } -func (svc *Service) eventStatusFromMsgHash( +func (i *Indexer) eventStatusFromMsgHash( ctx context.Context, gasLimit *big.Int, signal [32]byte, ) (relayer.EventStatus, error) { var eventStatus relayer.EventStatus - messageStatus, err := svc.destBridge.GetMessageStatus(nil, signal) + ctx, cancel := context.WithTimeout(ctx, i.ethClientTimeout) + + defer cancel() + + messageStatus, err := i.destBridge.GetMessageStatus(&bind.CallOpts{ + Context: ctx, + }, signal) if err != nil { return 0, errors.Wrap(err, "svc.destBridge.GetMessageStatus") } diff --git a/packages/relayer/indexer/handle_event_test.go b/packages/relayer/indexer/handle_event_test.go index a09ff9db71..03d846a412 100644 --- a/packages/relayer/indexer/handle_event_test.go +++ b/packages/relayer/indexer/handle_event_test.go @@ -11,76 +11,6 @@ import ( "github.com/taikoxyz/taiko-mono/packages/relayer/mock" ) -var ( - relayerAddr = common.HexToAddress("0x71C7656EC7ab88b098defB751B7401B5f6d8976F") -) - -func Test_canProcessMessage(t *testing.T) { - tests := []struct { - name string - eventStatus relayer.EventStatus - messageOwner common.Address - relayerAddress common.Address - want bool - }{ - { - "canProcess, eventStatusNew", - relayer.EventStatusNew, - relayerAddr, - relayerAddr, - true, - }, - { - "cantProcess, eventStatusDone", - relayer.EventStatusDone, - relayerAddr, - relayerAddr, - false, - }, - { - "cantProcess, eventStatusRetriable", - relayer.EventStatusRetriable, - relayerAddr, - relayerAddr, - false, - }, - { - "cantProcess, eventStatusNewOnlyOwner and relayer is not owner", - relayer.EventStatusNewOnlyOwner, - common.HexToAddress("0x"), - relayerAddr, - false, - }, - { - "cantProcess, eventStatusFailed", - relayer.EventStatusFailed, - common.HexToAddress("0x"), - relayerAddr, - false, - }, - { - "canProcess, eventStatusOnlyOwner and relayer address is owner", - relayer.EventStatusNewOnlyOwner, - relayerAddr, - relayerAddr, - true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - canProcess := canProcessMessage( - context.Background(), - tt.eventStatus, - tt.messageOwner, - tt.relayerAddress, - ) - - assert.Equal(t, tt.want, canProcess) - }) - } -} - func Test_eventStatusFromMsgHash(t *testing.T) { tests := []struct { name string @@ -134,7 +64,7 @@ func Test_eventStatusFromMsgHash(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - svc, _ := newTestService() + svc, _ := newTestService(Sync, FilterAndSubscribe) status, err := svc.eventStatusFromMsgHash(tt.ctx, tt.gasLimit, tt.signal) if tt.wantErr != nil { diff --git a/packages/relayer/indexer/handle_no_events_in_batch.go b/packages/relayer/indexer/handle_no_events_in_batch.go index 71ef151c20..1155a55b0c 100644 --- a/packages/relayer/indexer/handle_no_events_in_batch.go +++ b/packages/relayer/indexer/handle_no_events_in_batch.go @@ -12,19 +12,19 @@ import ( // handleNoEventsInBatch is used when an entire batch call has no events in the entire response, // and we need to update the latest block processed -func (svc *Service) handleNoEventsInBatch( +func (i *Indexer) handleNoEventsInBatch( ctx context.Context, chainID *big.Int, blockNumber int64, ) error { - header, err := svc.ethClient.HeaderByNumber(ctx, big.NewInt(blockNumber)) + header, err := i.srcEthClient.HeaderByNumber(ctx, big.NewInt(blockNumber)) if err != nil { - return errors.Wrap(err, "svc.ethClient.HeaderByNumber") + return errors.Wrap(err, "i.srcEthClient.HeaderByNumber") } slog.Info("setting last processed block", "blockNum", blockNumber, "headerHash", header.Hash().Hex()) - if err := svc.blockRepo.Save(relayer.SaveBlockOpts{ + if err := i.blockRepo.Save(relayer.SaveBlockOpts{ Height: uint64(blockNumber), Hash: header.Hash(), ChainID: chainID, @@ -35,7 +35,7 @@ func (svc *Service) handleNoEventsInBatch( relayer.BlocksProcessed.Inc() - svc.processingBlockHeight = uint64(blockNumber) + i.processingBlockHeight = uint64(blockNumber) return nil } diff --git a/packages/relayer/indexer/handle_no_events_in_batch_test.go b/packages/relayer/indexer/handle_no_events_in_batch_test.go index fe0bb71685..3d03ad130e 100644 --- a/packages/relayer/indexer/handle_no_events_in_batch_test.go +++ b/packages/relayer/indexer/handle_no_events_in_batch_test.go @@ -25,7 +25,7 @@ func Test_handleNoEventsInBatch(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - svc, _ := newTestService() + svc, _ := newTestService(Sync, FilterAndSubscribe) assert.NotEqual(t, svc.processingBlockHeight, uint64(tt.blockNumber)) diff --git a/packages/relayer/http/errors.go b/packages/relayer/indexer/http/errors.go similarity index 100% rename from packages/relayer/http/errors.go rename to packages/relayer/indexer/http/errors.go diff --git a/packages/relayer/http/get_block_info.go b/packages/relayer/indexer/http/get_block_info.go similarity index 57% rename from packages/relayer/http/get_block_info.go rename to packages/relayer/indexer/http/get_block_info.go index 0a87ea1247..0861b182a0 100644 --- a/packages/relayer/http/get_block_info.go +++ b/packages/relayer/indexer/http/get_block_info.go @@ -19,32 +19,35 @@ type getBlockInfoResponse struct { } func (srv *Server) GetBlockInfo(c echo.Context) error { - l1ChainID, err := srv.l1EthClient.ChainID(c.Request().Context()) + srcChainID, err := srv.srcEthClient.ChainID(c.Request().Context()) if err != nil { return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) } - l2ChainID, err := srv.l2EthClient.ChainID(c.Request().Context()) + destChainID, err := srv.destEthClient.ChainID(c.Request().Context()) if err != nil { return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) } - latestL1Block, err := srv.l1EthClient.BlockNumber(c.Request().Context()) + latestSrcBlock, err := srv.srcEthClient.BlockNumber(c.Request().Context()) if err != nil { return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) } - latestL2Block, err := srv.l2EthClient.BlockNumber(c.Request().Context()) + latestDestBlock, err := srv.destEthClient.BlockNumber(c.Request().Context()) if err != nil { return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) } - latestProcessedL1Block, err := srv.blockRepo.GetLatestBlockProcessedForEvent(relayer.EventNameMessageSent, l1ChainID) + latestProcessedSrcBlock, err := srv.blockRepo.GetLatestBlockProcessedForEvent(relayer.EventNameMessageSent, srcChainID) if err != nil { return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) } - latestProcessedL2Block, err := srv.blockRepo.GetLatestBlockProcessedForEvent(relayer.EventNameMessageSent, l2ChainID) + latestProcessedDestBlock, err := srv.blockRepo.GetLatestBlockProcessedForEvent( + relayer.EventNameMessageSent, + destChainID, + ) if err != nil { return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) } @@ -52,14 +55,14 @@ func (srv *Server) GetBlockInfo(c echo.Context) error { resp := getBlockInfoResponse{ Data: []blockInfo{ { - ChainID: l1ChainID.Int64(), - LatestProcessedBlock: int64(latestProcessedL1Block.Height), - LatestBlock: int64(latestL1Block), + ChainID: srcChainID.Int64(), + LatestProcessedBlock: int64(latestProcessedSrcBlock.Height), + LatestBlock: int64(latestSrcBlock), }, { - ChainID: l2ChainID.Int64(), - LatestProcessedBlock: int64(latestProcessedL2Block.Height), - LatestBlock: int64(latestL2Block), + ChainID: destChainID.Int64(), + LatestProcessedBlock: int64(latestProcessedDestBlock.Height), + LatestBlock: int64(latestDestBlock), }, }, } diff --git a/packages/relayer/http/get_events_by_address.go b/packages/relayer/indexer/http/get_events_by_address.go similarity index 100% rename from packages/relayer/http/get_events_by_address.go rename to packages/relayer/indexer/http/get_events_by_address.go diff --git a/packages/relayer/http/get_events_by_address_test.go b/packages/relayer/indexer/http/get_events_by_address_test.go similarity index 100% rename from packages/relayer/http/get_events_by_address_test.go rename to packages/relayer/indexer/http/get_events_by_address_test.go diff --git a/packages/relayer/http/routes.go b/packages/relayer/indexer/http/routes.go similarity index 100% rename from packages/relayer/http/routes.go rename to packages/relayer/indexer/http/routes.go diff --git a/packages/relayer/http/server.go b/packages/relayer/indexer/http/server.go similarity index 73% rename from packages/relayer/http/server.go rename to packages/relayer/indexer/http/server.go index b2244a96dc..dc5ba3cd68 100644 --- a/packages/relayer/http/server.go +++ b/packages/relayer/indexer/http/server.go @@ -2,32 +2,36 @@ package http import ( "context" - "fmt" + "math/big" "net/http" "os" "github.com/labstack/echo/v4/middleware" "github.com/taikoxyz/taiko-mono/packages/relayer" - echoprom "github.com/labstack/echo-contrib/prometheus" echo "github.com/labstack/echo/v4" ) +type ethClient interface { + BlockNumber(ctx context.Context) (uint64, error) + ChainID(ctx context.Context) (*big.Int, error) +} + type Server struct { - echo *echo.Echo - eventRepo relayer.EventRepository - blockRepo relayer.BlockRepository - l1EthClient relayer.EthClient - l2EthClient relayer.EthClient + echo *echo.Echo + eventRepo relayer.EventRepository + blockRepo relayer.BlockRepository + srcEthClient ethClient + destEthClient ethClient } type NewServerOpts struct { - Echo *echo.Echo - EventRepo relayer.EventRepository - BlockRepo relayer.BlockRepository - CorsOrigins []string - L1EthClient relayer.EthClient - L2EthClient relayer.EthClient + Echo *echo.Echo + EventRepo relayer.EventRepository + BlockRepo relayer.BlockRepository + CorsOrigins []string + SrcEthClient ethClient + DestEthClient ethClient } func (opts NewServerOpts) Validate() error { @@ -43,11 +47,11 @@ func (opts NewServerOpts) Validate() error { return relayer.ErrNoCORSOrigins } - if opts.L1EthClient == nil { + if opts.SrcEthClient == nil { return relayer.ErrNoEthClient } - if opts.L2EthClient == nil { + if opts.DestEthClient == nil { return relayer.ErrNoEthClient } @@ -64,11 +68,11 @@ func NewServer(opts NewServerOpts) (*Server, error) { } srv := &Server{ - blockRepo: opts.BlockRepo, - echo: opts.Echo, - eventRepo: opts.EventRepo, - l1EthClient: opts.L1EthClient, - l2EthClient: opts.L2EthClient, + blockRepo: opts.BlockRepo, + echo: opts.Echo, + eventRepo: opts.EventRepo, + srcEthClient: opts.SrcEthClient, + destEthClient: opts.DestEthClient, } corsOrigins := opts.CorsOrigins @@ -130,18 +134,4 @@ func (srv *Server) configureMiddleware(corsOrigins []string) { AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept}, AllowMethods: []string{http.MethodGet, http.MethodHead}, })) - - srv.configureAndStartPrometheus() -} - -func (srv *Server) configureAndStartPrometheus() { - // Enable metrics middleware - p := echoprom.NewPrometheus("echo", nil) - p.Use(srv.echo) - e := echo.New() - p.SetMetricsPath(e) - - go func() { - _ = e.Start(fmt.Sprintf(":%v", os.Getenv("PROMETHEUS_HTTP_PORT"))) - }() } diff --git a/packages/relayer/http/server_test.go b/packages/relayer/indexer/http/server_test.go similarity index 54% rename from packages/relayer/http/server_test.go rename to packages/relayer/indexer/http/server_test.go index a3928b7818..3fd3d9ba06 100644 --- a/packages/relayer/http/server_test.go +++ b/packages/relayer/indexer/http/server_test.go @@ -24,7 +24,6 @@ func newTestServer(url string) *Server { srv.configureMiddleware([]string{"*"}) srv.configureRoutes() - srv.configureAndStartPrometheus() return srv } @@ -38,78 +37,78 @@ func Test_NewServer(t *testing.T) { { "success", NewServerOpts{ - Echo: echo.New(), - EventRepo: &repo.EventRepository{}, - CorsOrigins: make([]string, 0), - L1EthClient: &mock.EthClient{}, - L2EthClient: &mock.EthClient{}, - BlockRepo: &mock.BlockRepository{}, + Echo: echo.New(), + EventRepo: &repo.EventRepository{}, + CorsOrigins: make([]string, 0), + SrcEthClient: &mock.EthClient{}, + DestEthClient: &mock.EthClient{}, + BlockRepo: &mock.BlockRepository{}, }, nil, }, { - "noL1EthClient", + "noSrcEthClient", NewServerOpts{ - Echo: echo.New(), - EventRepo: &repo.EventRepository{}, - CorsOrigins: make([]string, 0), - L2EthClient: &mock.EthClient{}, - BlockRepo: &mock.BlockRepository{}, + Echo: echo.New(), + EventRepo: &repo.EventRepository{}, + CorsOrigins: make([]string, 0), + DestEthClient: &mock.EthClient{}, + BlockRepo: &mock.BlockRepository{}, }, relayer.ErrNoEthClient, }, { - "noL2EthClient", + "noDestEthClient", NewServerOpts{ - Echo: echo.New(), - EventRepo: &repo.EventRepository{}, - CorsOrigins: make([]string, 0), - L1EthClient: &mock.EthClient{}, - BlockRepo: &mock.BlockRepository{}, + Echo: echo.New(), + EventRepo: &repo.EventRepository{}, + CorsOrigins: make([]string, 0), + SrcEthClient: &mock.EthClient{}, + BlockRepo: &mock.BlockRepository{}, }, relayer.ErrNoEthClient, }, { "noBlockRepo", NewServerOpts{ - Echo: echo.New(), - EventRepo: &repo.EventRepository{}, - CorsOrigins: make([]string, 0), - L1EthClient: &mock.EthClient{}, - L2EthClient: &mock.EthClient{}, + Echo: echo.New(), + EventRepo: &repo.EventRepository{}, + CorsOrigins: make([]string, 0), + SrcEthClient: &mock.EthClient{}, + DestEthClient: &mock.EthClient{}, }, relayer.ErrNoBlockRepository, }, { "noEventRepo", NewServerOpts{ - Echo: echo.New(), - CorsOrigins: make([]string, 0), - L1EthClient: &mock.EthClient{}, - L2EthClient: &mock.EthClient{}, - BlockRepo: &mock.BlockRepository{}, + Echo: echo.New(), + CorsOrigins: make([]string, 0), + SrcEthClient: &mock.EthClient{}, + DestEthClient: &mock.EthClient{}, + BlockRepo: &mock.BlockRepository{}, }, relayer.ErrNoEventRepository, }, { "noCorsOrigins", NewServerOpts{ - Echo: echo.New(), - EventRepo: &repo.EventRepository{}, - L1EthClient: &mock.EthClient{}, - L2EthClient: &mock.EthClient{}, - BlockRepo: &mock.BlockRepository{}, + Echo: echo.New(), + EventRepo: &repo.EventRepository{}, + SrcEthClient: &mock.EthClient{}, + DestEthClient: &mock.EthClient{}, + BlockRepo: &mock.BlockRepository{}, }, relayer.ErrNoCORSOrigins, }, { "noHttpFramework", NewServerOpts{ - EventRepo: &repo.EventRepository{}, - CorsOrigins: make([]string, 0), - L1EthClient: &mock.EthClient{}, - L2EthClient: &mock.EthClient{}, - BlockRepo: &mock.BlockRepository{}, + EventRepo: &repo.EventRepository{}, + CorsOrigins: make([]string, 0), + SrcEthClient: &mock.EthClient{}, + DestEthClient: &mock.EthClient{}, + BlockRepo: &mock.BlockRepository{}, }, ErrNoHTTPFramework, }, @@ -147,19 +146,6 @@ func Test_Root(t *testing.T) { } } -func Test_Metrics(t *testing.T) { - srv := newTestServer("") - - req, _ := http.NewRequest(echo.GET, "/metrics", nil) - rec := httptest.NewRecorder() - - srv.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test_Metrics expected code %v, got %v", http.StatusOK, rec.Code) - } -} - func Test_StartShutdown(t *testing.T) { srv := newTestServer("") diff --git a/packages/relayer/indexer/indexer.go b/packages/relayer/indexer/indexer.go new file mode 100644 index 0000000000..eb789a9171 --- /dev/null +++ b/packages/relayer/indexer/indexer.go @@ -0,0 +1,406 @@ +package indexer + +import ( + "context" + "database/sql" + "fmt" + "log/slog" + "math/big" + nethttp "net/http" + "sync" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/cyberhorsey/errors" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/labstack/echo/v4" + "github.com/taikoxyz/taiko-mono/packages/relayer" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/taikol1" + "github.com/taikoxyz/taiko-mono/packages/relayer/indexer/http" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" + "github.com/taikoxyz/taiko-mono/packages/relayer/repo" + "github.com/urfave/cli/v2" + "golang.org/x/sync/errgroup" + "gorm.io/gorm" +) + +var ( + ZeroAddress = common.HexToAddress("0x0000000000000000000000000000000000000000") +) + +var ( + eventName = relayer.EventNameMessageSent +) + +type WatchMode string + +var ( + Filter WatchMode = "filter" + Subscribe WatchMode = "subscribe" + FilterAndSubscribe WatchMode = "filter-and-subscribe" + WatchModes = []WatchMode{Filter, Subscribe, FilterAndSubscribe} +) + +type SyncMode string + +var ( + Sync SyncMode = "sync" + Resync SyncMode = "resync" + Modes = []SyncMode{Sync, Resync} +) + +type ethClient interface { + ChainID(ctx context.Context) (*big.Int, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) +} + +type DB interface { + DB() (*sql.DB, error) + GormDB() *gorm.DB +} + +type Indexer struct { + eventRepo relayer.EventRepository + blockRepo relayer.BlockRepository + srcEthClient ethClient + + processingBlockHeight uint64 + + bridge relayer.Bridge + destBridge relayer.Bridge + + blockBatchSize uint64 + numGoroutines int + subscriptionBackoff time.Duration + + taikol1 *taikol1.TaikoL1 + + queue queue.Queue + + srcChainId *big.Int + destChainId *big.Int + + watchMode WatchMode + syncMode SyncMode + + srv *http.Server + httpPort uint64 + + ethClientTimeout time.Duration + + wg *sync.WaitGroup + + ctx context.Context +} + +func (i *Indexer) InitFromCli(ctx context.Context, c *cli.Context) error { + cfg, err := NewConfigFromCliContext(c) + if err != nil { + return err + } + + return InitFromConfig(ctx, i, cfg) +} + +func InitFromConfig(ctx context.Context, i *Indexer, cfg *Config) (err error) { + db, err := cfg.OpenDBFunc() + if err != nil { + return err + } + + eventRepository, err := repo.NewEventRepository(db) + if err != nil { + return err + } + + blockRepository, err := repo.NewBlockRepository(db) + if err != nil { + return err + } + + srcEthClient, err := ethclient.Dial(cfg.SrcRPCUrl) + if err != nil { + return err + } + + destEthClient, err := ethclient.Dial(cfg.DestRPCUrl) + if err != nil { + return err + } + + srv, err := http.NewServer(http.NewServerOpts{ + EventRepo: eventRepository, + Echo: echo.New(), + CorsOrigins: cfg.CORSOrigins, + SrcEthClient: srcEthClient, + DestEthClient: destEthClient, + BlockRepo: blockRepository, + }) + if err != nil { + return err + } + + q, err := cfg.OpenQueueFunc() + if err != nil { + return err + } + + srcBridge, err := bridge.NewBridge(cfg.SrcBridgeAddress, srcEthClient) + if err != nil { + return errors.Wrap(err, "bridge.NewBridge") + } + + destBridge, err := bridge.NewBridge(cfg.DestBridgeAddress, destEthClient) + if err != nil { + return errors.Wrap(err, "bridge.NewBridge") + } + + var taikoL1 *taikol1.TaikoL1 + if cfg.SrcTaikoAddress != ZeroAddress { + taikoL1, err = taikol1.NewTaikoL1(cfg.SrcTaikoAddress, srcEthClient) + if err != nil { + return errors.Wrap(err, "taikol1.NewTaikoL1") + } + } + + srcChainID, err := srcEthClient.ChainID(context.Background()) + if err != nil { + return errors.Wrap(err, "srcEthClient.ChainID") + } + + destChainID, err := destEthClient.ChainID(context.Background()) + if err != nil { + return errors.Wrap(err, "destEthClient.ChainID") + } + + i.blockRepo = blockRepository + i.eventRepo = eventRepository + i.srcEthClient = srcEthClient + + i.bridge = srcBridge + i.destBridge = destBridge + i.taikol1 = taikoL1 + + i.blockBatchSize = cfg.BlockBatchSize + i.numGoroutines = int(cfg.NumGoroutines) + i.subscriptionBackoff = time.Duration(cfg.SubscriptionBackoff) * time.Second + + i.queue = q + + i.srv = srv + i.httpPort = cfg.HTTPPort + + i.srcChainId = srcChainID + i.destChainId = destChainID + + i.syncMode = cfg.SyncMode + i.watchMode = cfg.WatchMode + + i.wg = &sync.WaitGroup{} + + i.ethClientTimeout = time.Duration(cfg.ETHClientTimeout) * time.Second + + return nil +} + +func (i *Indexer) Name() string { + return "indexer" +} + +// TODO +func (i *Indexer) Close(ctx context.Context) { + if err := i.srv.Shutdown(ctx); err != nil { + slog.Error("srv shutdown", "error", err) + } + + i.wg.Wait() +} + +// nolint: funlen +func (i *Indexer) Start() error { + go func() { + if err := i.srv.Start(fmt.Sprintf(":%v", i.httpPort)); err != nethttp.ErrServerClosed { + slog.Error("http srv start", "error", err.Error()) + } + }() + + i.ctx = context.Background() + + if err := i.queue.Start(i.ctx, i.queueName()); err != nil { + return err + } + + i.wg.Add(1) + + go func() { + defer func() { + i.wg.Done() + }() + + if err := i.filter(i.ctx); err != nil { + slog.Error("error filtering blocks", "error", err.Error()) + } + }() + + go func() { + if err := backoff.Retry(func() error { + return scanBlocks(i.ctx, i.srcEthClient, i.srcChainId, i.wg) + }, backoff.NewConstantBackOff(5*time.Second)); err != nil { + slog.Error("scan blocks backoff retry", "error", err) + } + }() + + go func() { + if err := backoff.Retry(func() error { + return i.queue.Notify(i.ctx, i.wg) + }, backoff.NewConstantBackOff(5*time.Second)); err != nil { + slog.Error("queue notify backoff retry", "error", err) + } + }() + + return nil +} + +func (i *Indexer) filter(ctx context.Context) error { + // if subscribing to new events, skip filtering and subscribe + if i.watchMode == Subscribe { + return i.subscribe(ctx, i.srcChainId) + } + + if err := i.setInitialProcessingBlockByMode(ctx, i.syncMode, i.srcChainId); err != nil { + return errors.Wrap(err, "i.setInitialProcessingBlockByMode") + } + + header, err := i.srcEthClient.HeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "i.srcEthClient.HeaderByNumber") + } + + if i.processingBlockHeight == header.Number.Uint64() { + slog.Info("indexing caught up, subscribing to new incoming events", "chainID", i.srcChainId.Uint64()) + return i.subscribe(ctx, i.srcChainId) + } + + slog.Info("fetching batch block events", + "chainID", i.srcChainId.Uint64(), + "startblock", i.processingBlockHeight, + "endblock", header.Number.Int64(), + "batchsize", i.blockBatchSize, + ) + + for j := i.processingBlockHeight; j < header.Number.Uint64(); j += i.blockBatchSize { + end := i.processingBlockHeight + i.blockBatchSize + // if the end of the batch is greater than the latest block number, set end + // to the latest block number + if end > header.Number.Uint64() { + end = header.Number.Uint64() + } + + // filter exclusive of the end block. + // we use "end" as the next starting point of the batch, and + // process up to end - 1 for this batch. + filterEnd := end - 1 + + slog.Info("block batch", "start", j, "end", filterEnd) + + filterOpts := &bind.FilterOpts{ + Start: i.processingBlockHeight, + End: &filterEnd, + Context: ctx, + } + + messageStatusChangedEvents, err := i.bridge.FilterMessageStatusChanged(filterOpts, nil) + if err != nil { + return errors.Wrap(err, "bridge.FilterMessageStatusChanged") + } + + // we dont need to do anything with msgStatus events except save them to the DB. + // we dont need to process them. they are for exposing via the API. + + err = i.saveMessageStatusChangedEvents(ctx, i.srcChainId, messageStatusChangedEvents) + if err != nil { + return errors.Wrap(err, "bridge.saveMessageStatusChangedEvents") + } + + messageSentEvents, err := i.bridge.FilterMessageSent(filterOpts, nil) + if err != nil { + return errors.Wrap(err, "bridge.FilterMessageSent") + } + + if !messageSentEvents.Next() || messageSentEvents.Event == nil { + // use "end" not "filterEnd" here, because it will be used as the start + // of the next batch. + if err := i.handleNoEventsInBatch(ctx, i.srcChainId, int64(end)); err != nil { + return errors.Wrap(err, "i.handleNoEventsInBatch") + } + + continue + } + + group, groupCtx := errgroup.WithContext(ctx) + + group.SetLimit(i.numGoroutines) + + for { + event := messageSentEvents.Event + + group.Go(func() error { + err := i.handleEvent(groupCtx, i.srcChainId, event) + if err != nil { + relayer.ErrorEvents.Inc() + // log error but always return nil to keep other goroutines active + slog.Error("error handling event", "err", err.Error()) + } else { + slog.Info("handled event successfully") + } + + return nil + }) + + // if there are no more events + if !messageSentEvents.Next() { + // wait for the last of the goroutines to finish + if err := group.Wait(); err != nil { + return errors.Wrap(err, "group.Wait") + } + // handle no events remaining, saving the processing block and restarting the for + // loop + if err := i.handleNoEventsInBatch(ctx, i.srcChainId, int64(end)); err != nil { + return errors.Wrap(err, "i.handleNoEventsInBatch") + } + + break + } + } + } + + slog.Info( + "indexer fully caught up, checking latest block number to see if it's advanced", + ) + + latestBlock, err := i.srcEthClient.HeaderByNumber(ctx, nil) + if err != nil { + return errors.Wrap(err, "i.srcEthClient.HeaderByNumber") + } + + if i.processingBlockHeight < latestBlock.Number.Uint64() { + return i.filter(ctx) + } + + // we are caught up and specified not to subscribe, we can return now + if i.watchMode == Filter { + return nil + } + + return i.subscribe(ctx, i.srcChainId) +} + +func (i *Indexer) queueName() string { + return fmt.Sprintf("%v-%v-queue", i.srcChainId.String(), i.destChainId.String()) +} diff --git a/packages/relayer/indexer/indexer_test.go b/packages/relayer/indexer/indexer_test.go new file mode 100644 index 0000000000..14778603e0 --- /dev/null +++ b/packages/relayer/indexer/indexer_test.go @@ -0,0 +1,53 @@ +package indexer + +import ( + "context" + "sync" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/labstack/echo/v4" + "github.com/taikoxyz/taiko-mono/packages/relayer" + "github.com/taikoxyz/taiko-mono/packages/relayer/indexer/http" + "github.com/taikoxyz/taiko-mono/packages/relayer/mock" +) + +func newTestService(syncMode SyncMode, watchMode WatchMode) (*Indexer, relayer.Bridge) { + b := &mock.Bridge{} + srv, _ := http.NewServer(http.NewServerOpts{ + Echo: echo.New(), + EventRepo: &mock.EventRepository{}, + BlockRepo: &mock.BlockRepository{}, + SrcEthClient: ðclient.Client{}, + DestEthClient: ðclient.Client{}, + CorsOrigins: []string{}, + }) + + return &Indexer{ + blockRepo: &mock.BlockRepository{}, + eventRepo: &mock.EventRepository{}, + bridge: b, + destBridge: b, + srcEthClient: &mock.EthClient{}, + numGoroutines: 10, + + processingBlockHeight: 0, + blockBatchSize: 100, + + queue: &mock.Queue{}, + + syncMode: syncMode, + watchMode: watchMode, + httpPort: 4102, + srv: srv, + + wg: &sync.WaitGroup{}, + + ctx: context.Background(), + + srcChainId: mock.MockChainID, + destChainId: mock.MockChainID, + + ethClientTimeout: 10 * time.Second, + }, b +} diff --git a/packages/relayer/indexer/save_message_status_changed_events.go b/packages/relayer/indexer/save_message_status_changed_events.go index 9d9aa3843b..ef8914b31e 100644 --- a/packages/relayer/indexer/save_message_status_changed_events.go +++ b/packages/relayer/indexer/save_message_status_changed_events.go @@ -10,10 +10,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) -func (svc *Service) saveMessageStatusChangedEvents( +func (i *Indexer) saveMessageStatusChangedEvents( ctx context.Context, chainID *big.Int, events *bridge.BridgeMessageStatusChangedIterator, @@ -28,16 +28,16 @@ func (svc *Service) saveMessageStatusChangedEvents( slog.Info("messageStatusChanged", "msgHash", common.Hash(event.MsgHash).Hex()) - if err := svc.detectAndHandleReorg( + if err := i.detectAndHandleReorg( ctx, relayer.EventNameMessageStatusChanged, common.Hash(event.MsgHash).Hex(), ); err != nil { - return errors.Wrap(err, "svc.detectAndHandleReorg") + return errors.Wrap(err, "i.detectAndHandleReorg") } - if err := svc.saveMessageStatusChangedEvent(ctx, chainID, event); err != nil { - return errors.Wrap(err, "svc.saveMessageStatusChangedEvent") + if err := i.saveMessageStatusChangedEvent(ctx, chainID, event); err != nil { + return errors.Wrap(err, "i.saveMessageStatusChangedEvent") } if !events.Next() { @@ -46,7 +46,7 @@ func (svc *Service) saveMessageStatusChangedEvents( } } -func (svc *Service) saveMessageStatusChangedEvent( +func (i *Indexer) saveMessageStatusChangedEvent( ctx context.Context, chainID *big.Int, event *bridge.BridgeMessageStatusChanged, @@ -59,16 +59,16 @@ func (svc *Service) saveMessageStatusChangedEvent( // get the previous MessageSent event or other message status changed events, // so we can find out the previous owner of this msg hash, // to save to the db. - e, err := svc.eventRepo.FirstByMsgHash(ctx, common.Hash(event.MsgHash).Hex()) + e, err := i.eventRepo.FirstByMsgHash(ctx, common.Hash(event.MsgHash).Hex()) if err != nil { - return errors.Wrap(err, "svc.eventRepo.FirstByMsgHash") + return errors.Wrap(err, "i.eventRepo.FirstByMsgHash") } if e == nil || e.MsgHash == "" { return nil } - _, err = svc.eventRepo.Save(ctx, relayer.SaveEventOpts{ + _, err = i.eventRepo.Save(ctx, relayer.SaveEventOpts{ Name: relayer.EventNameMessageStatusChanged, Data: string(marshaled), ChainID: chainID, @@ -78,7 +78,7 @@ func (svc *Service) saveMessageStatusChangedEvent( Event: relayer.EventNameMessageStatusChanged, }) if err != nil { - return errors.Wrap(err, "svc.eventRepo.Save") + return errors.Wrap(err, "i.eventRepo.Save") } return nil diff --git a/packages/relayer/indexer/scan_blocks.go b/packages/relayer/indexer/scan_blocks.go index a3024f498c..3c331d3362 100644 --- a/packages/relayer/indexer/scan_blocks.go +++ b/packages/relayer/indexer/scan_blocks.go @@ -3,27 +3,33 @@ package indexer import ( "context" "math/big" + "sync" "github.com/ethereum/go-ethereum/core/types" "github.com/taikoxyz/taiko-mono/packages/relayer" ) -func scanBlocks(ctx context.Context, ethClient ethClient, chainID *big.Int) { +func scanBlocks(ctx context.Context, ethClient ethClient, chainID *big.Int, wg *sync.WaitGroup) error { + wg.Add(1) + + defer func() { + wg.Done() + }() + headers := make(chan *types.Header) sub, err := ethClient.SubscribeNewHead(ctx, headers) if err != nil { - panic(err) + return err } for { select { - case <-sub.Err(): + case <-ctx.Done(): + return nil + case err := <-sub.Err(): relayer.BlocksScannedError.Inc() - - scanBlocks(ctx, ethClient, chainID) - - return + return err case <-headers: relayer.BlocksScanned.Inc() } diff --git a/packages/relayer/indexer/service.go b/packages/relayer/indexer/service.go deleted file mode 100644 index 92bad858ef..0000000000 --- a/packages/relayer/indexer/service.go +++ /dev/null @@ -1,221 +0,0 @@ -package indexer - -import ( - "context" - "crypto/ecdsa" - "math/big" - "time" - - "github.com/cyberhorsey/errors" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/erc1155vault" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/erc20vault" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/erc721vault" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/icrosschainsync" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/taikol1" - "github.com/taikoxyz/taiko-mono/packages/relayer/message" - "github.com/taikoxyz/taiko-mono/packages/relayer/proof" -) - -var ( - ZeroAddress = common.HexToAddress("0x0000000000000000000000000000000000000000") -) - -type ethClient interface { - ChainID(ctx context.Context) (*big.Int, error) - HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) - SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) -} - -type Service struct { - eventRepo relayer.EventRepository - blockRepo relayer.BlockRepository - ethClient ethClient - destRPC *rpc.Client - - processingBlockHeight uint64 - - bridge relayer.Bridge - destBridge relayer.Bridge - - processor *message.Processor - - relayerAddr common.Address - - blockBatchSize uint64 - numGoroutines int - subscriptionBackoff time.Duration - - taikol1 *taikol1.TaikoL1 -} - -type NewServiceOpts struct { - EventRepo relayer.EventRepository - BlockRepo relayer.BlockRepository - EthClient *ethclient.Client - DestEthClient *ethclient.Client - RPCClient *rpc.Client - DestRPCClient *rpc.Client - ECDSAKey string - BridgeAddress common.Address - DestBridgeAddress common.Address - SrcTaikoAddress common.Address - DestTaikoAddress common.Address - DestERC20VaultAddress common.Address - DestERC721VaultAddress common.Address - DestERC1155VaultAddress common.Address - SrcSignalServiceAddress common.Address - BlockBatchSize uint64 - NumGoroutines int - SubscriptionBackoff time.Duration - Confirmations uint64 - ProfitableOnly relayer.ProfitableOnly - HeaderSyncIntervalInSeconds int64 - ConfirmationsTimeoutInSeconds int64 -} - -func NewService(opts NewServiceOpts) (*Service, error) { - if opts.EventRepo == nil { - return nil, relayer.ErrNoEventRepository - } - - if opts.BlockRepo == nil { - return nil, relayer.ErrNoBlockRepository - } - - if opts.EthClient == nil { - return nil, relayer.ErrNoEthClient - } - - if opts.ECDSAKey == "" { - return nil, relayer.ErrNoECDSAKey - } - - if opts.DestEthClient == nil { - return nil, relayer.ErrNoEthClient - } - - if opts.BridgeAddress == ZeroAddress { - return nil, relayer.ErrNoBridgeAddress - } - - if opts.DestBridgeAddress == ZeroAddress { - return nil, relayer.ErrNoBridgeAddress - } - - if opts.RPCClient == nil { - return nil, relayer.ErrNoRPCClient - } - - privateKey, err := crypto.HexToECDSA(opts.ECDSAKey) - if err != nil { - return nil, errors.Wrap(err, "crypto.HexToECDSA") - } - - publicKey := privateKey.Public() - - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - return nil, errors.Wrap(err, "publicKey.(*ecdsa.PublicKey)") - } - - relayerAddr := crypto.PubkeyToAddress(*publicKeyECDSA) - - srcBridge, err := bridge.NewBridge(opts.BridgeAddress, opts.EthClient) - if err != nil { - return nil, errors.Wrap(err, "bridge.NewBridge") - } - - destBridge, err := bridge.NewBridge(opts.DestBridgeAddress, opts.DestEthClient) - if err != nil { - return nil, errors.Wrap(err, "bridge.NewBridge") - } - - prover, err := proof.New(opts.EthClient) - if err != nil { - return nil, errors.Wrap(err, "proof.New") - } - - destHeaderSyncer, err := icrosschainsync.NewICrossChainSync(opts.DestTaikoAddress, opts.DestEthClient) - if err != nil { - return nil, errors.Wrap(err, "icrosschainsync.NewTaikoL2") - } - - var taikoL1 *taikol1.TaikoL1 - if opts.SrcTaikoAddress != ZeroAddress { - taikoL1, err = taikol1.NewTaikoL1(opts.SrcTaikoAddress, opts.EthClient) - if err != nil { - return nil, errors.Wrap(err, "taikol1.NewTaikoL1") - } - } - - destERC20Vault, err := erc20vault.NewERC20Vault(opts.DestERC20VaultAddress, opts.DestEthClient) - if err != nil { - return nil, errors.Wrap(err, "erc20vault.NewERC20Vault") - } - - var destERC721Vault *erc721vault.ERC721Vault - if opts.DestERC721VaultAddress.Hex() != relayer.ZeroAddress.Hex() { - destERC721Vault, err = erc721vault.NewERC721Vault(opts.DestERC721VaultAddress, opts.DestEthClient) - if err != nil { - return nil, errors.Wrap(err, "erc721vault.NewERC721Vault") - } - } - - var destERC1155Vault *erc1155vault.ERC1155Vault - if opts.DestERC1155VaultAddress.Hex() != relayer.ZeroAddress.Hex() { - destERC1155Vault, err = erc1155vault.NewERC1155Vault(opts.DestERC1155VaultAddress, opts.DestEthClient) - if err != nil { - return nil, errors.Wrap(err, "erc1155vault.NewERC1155Vault") - } - } - - processor, err := message.NewProcessor(message.NewProcessorOpts{ - Prover: prover, - ECDSAKey: privateKey, - RPCClient: opts.RPCClient, - DestETHClient: opts.DestEthClient, - DestBridge: destBridge, - EventRepo: opts.EventRepo, - DestHeaderSyncer: destHeaderSyncer, - RelayerAddress: relayerAddr, - Confirmations: opts.Confirmations, - SrcETHClient: opts.EthClient, - ProfitableOnly: opts.ProfitableOnly, - HeaderSyncIntervalSeconds: opts.HeaderSyncIntervalInSeconds, - SrcSignalServiceAddress: opts.SrcSignalServiceAddress, - ConfirmationsTimeoutInSeconds: opts.ConfirmationsTimeoutInSeconds, - DestERC20Vault: destERC20Vault, - DestERC721Vault: destERC721Vault, - DestERC1155Vault: destERC1155Vault, - }) - if err != nil { - return nil, errors.Wrap(err, "message.NewProcessor") - } - - return &Service{ - blockRepo: opts.BlockRepo, - eventRepo: opts.EventRepo, - ethClient: opts.EthClient, - destRPC: opts.DestRPCClient, - - bridge: srcBridge, - destBridge: destBridge, - taikol1: taikoL1, - - processor: processor, - - relayerAddr: relayerAddr, - - blockBatchSize: opts.BlockBatchSize, - numGoroutines: opts.NumGoroutines, - subscriptionBackoff: opts.SubscriptionBackoff, - }, nil -} diff --git a/packages/relayer/indexer/service_test.go b/packages/relayer/indexer/service_test.go deleted file mode 100644 index c7b6830a7c..0000000000 --- a/packages/relayer/indexer/service_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package indexer - -import ( - "errors" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/message" - "github.com/taikoxyz/taiko-mono/packages/relayer/mock" - "github.com/taikoxyz/taiko-mono/packages/relayer/proof" - "github.com/taikoxyz/taiko-mono/packages/relayer/repo" -) - -var dummyEcdsaKey = "8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f" -var dummyAddress = "0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377" - -func newTestService() (*Service, relayer.Bridge) { - b := &mock.Bridge{} - - privateKey, _ := crypto.HexToECDSA(dummyEcdsaKey) - - prover, _ := proof.New( - &mock.Blocker{}, - ) - - processor, _ := message.NewProcessor(message.NewProcessorOpts{ - EventRepo: &mock.EventRepository{}, - DestBridge: &mock.Bridge{}, - SrcETHClient: &mock.EthClient{}, - DestETHClient: &mock.EthClient{}, - DestERC20Vault: &mock.TokenVault{}, - DestERC721Vault: &mock.TokenVault{}, - DestERC1155Vault: &mock.TokenVault{}, - ECDSAKey: privateKey, - DestHeaderSyncer: &mock.HeaderSyncer{}, - Prover: prover, - RPCClient: &mock.Caller{}, - ConfirmationsTimeoutInSeconds: 900, - }) - - return &Service{ - blockRepo: &mock.BlockRepository{}, - eventRepo: &mock.EventRepository{}, - bridge: b, - destBridge: b, - ethClient: &mock.EthClient{}, - numGoroutines: 10, - - processingBlockHeight: 0, - processor: processor, - blockBatchSize: 100, - }, b -} - -func Test_NewService(t *testing.T) { - tests := []struct { - name string - opts NewServiceOpts - wantErr error - }{ - { - "success", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - RPCClient: &rpc.Client{}, - EthClient: ðclient.Client{}, - DestEthClient: ðclient.Client{}, - ECDSAKey: dummyEcdsaKey, - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - nil, - }, - { - "invalidECDSAKey", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - RPCClient: &rpc.Client{}, - EthClient: ðclient.Client{}, - DestEthClient: ðclient.Client{}, - ECDSAKey: ">>>", - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - errors.New("crypto.HexToECDSA: invalid hex character '>' in private key"), - }, - { - "noRpcClient", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - EthClient: ðclient.Client{}, - DestEthClient: ðclient.Client{}, - ECDSAKey: dummyEcdsaKey, - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoRPCClient, - }, - { - "noBridgeAddress", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - EthClient: ðclient.Client{}, - DestEthClient: ðclient.Client{}, - ECDSAKey: dummyEcdsaKey, - RPCClient: &rpc.Client{}, - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoBridgeAddress, - }, - { - "noDestBridgeAddress", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - EthClient: ðclient.Client{}, - DestEthClient: ðclient.Client{}, - ECDSAKey: dummyEcdsaKey, - RPCClient: &rpc.Client{}, - BridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoBridgeAddress, - }, - { - "noECDSAKey", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - RPCClient: &rpc.Client{}, - EthClient: ðclient.Client{}, - DestEthClient: ðclient.Client{}, - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoECDSAKey, - }, - { - "noEventRepo", - NewServiceOpts{ - BlockRepo: &repo.BlockRepository{}, - EthClient: ðclient.Client{}, - ECDSAKey: dummyEcdsaKey, - DestEthClient: ðclient.Client{}, - BridgeAddress: common.HexToAddress(dummyAddress), - RPCClient: &rpc.Client{}, - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoEventRepository, - }, - { - "noBlockRepo", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - EthClient: ðclient.Client{}, - ECDSAKey: dummyEcdsaKey, - RPCClient: &rpc.Client{}, - DestEthClient: ðclient.Client{}, - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoBlockRepository, - }, - { - "noEthClient", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - ECDSAKey: dummyEcdsaKey, - RPCClient: &rpc.Client{}, - DestEthClient: ðclient.Client{}, - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoEthClient, - }, - { - "noDestEthClient", - NewServiceOpts{ - EventRepo: &repo.EventRepository{}, - BlockRepo: &repo.BlockRepository{}, - ECDSAKey: dummyEcdsaKey, - EthClient: ðclient.Client{}, - RPCClient: &rpc.Client{}, - BridgeAddress: common.HexToAddress(dummyAddress), - DestBridgeAddress: common.HexToAddress(dummyAddress), - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoEthClient, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := NewService(tt.opts) - if tt.wantErr != nil { - assert.EqualError(t, tt.wantErr, err.Error()) - } else { - assert.Nil(t, err) - } - }) - } -} diff --git a/packages/relayer/indexer/set_initial_processing_block_by_mode.go b/packages/relayer/indexer/set_initial_processing_block_by_mode.go index b76e1d469a..2f04576485 100644 --- a/packages/relayer/indexer/set_initial_processing_block_by_mode.go +++ b/packages/relayer/indexer/set_initial_processing_block_by_mode.go @@ -8,15 +8,15 @@ import ( "github.com/taikoxyz/taiko-mono/packages/relayer" ) -func (svc *Service) setInitialProcessingBlockByMode( +func (i *Indexer) setInitialProcessingBlockByMode( ctx context.Context, - mode relayer.Mode, + mode SyncMode, chainID *big.Int, ) error { var startingBlock uint64 = 0 - if svc.taikol1 != nil { - stateVars, err := svc.taikol1.GetStateVariables(nil) + if i.taikol1 != nil { + stateVars, err := i.taikol1.GetStateVariables(nil) if err != nil { return errors.Wrap(err, "svc.taikoL1.GetStateVariables") } @@ -25,9 +25,9 @@ func (svc *Service) setInitialProcessingBlockByMode( } switch mode { - case relayer.SyncMode: + case Sync: // get most recently processed block height from the DB - latestProcessedBlock, err := svc.blockRepo.GetLatestBlockProcessedForEvent( + latestProcessedBlock, err := i.blockRepo.GetLatestBlockProcessedForEvent( eventName, chainID, ) @@ -39,11 +39,11 @@ func (svc *Service) setInitialProcessingBlockByMode( startingBlock = latestProcessedBlock.Height } - svc.processingBlockHeight = startingBlock + i.processingBlockHeight = startingBlock return nil - case relayer.ResyncMode: - svc.processingBlockHeight = startingBlock + case Resync: + i.processingBlockHeight = startingBlock return nil default: return relayer.ErrInvalidMode diff --git a/packages/relayer/indexer/set_initial_processing_block_by_mode_test.go b/packages/relayer/indexer/set_initial_processing_block_by_mode_test.go index 0f85fe2a2e..ffc6ed6027 100644 --- a/packages/relayer/indexer/set_initial_processing_block_by_mode_test.go +++ b/packages/relayer/indexer/set_initial_processing_block_by_mode_test.go @@ -6,42 +6,41 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer" "github.com/taikoxyz/taiko-mono/packages/relayer/mock" ) func Test_SetInitialProcessingBlockByMode(t *testing.T) { tests := []struct { name string - mode relayer.Mode + mode SyncMode chainID *big.Int wantErr bool wantHeight uint64 }{ { "resync", - relayer.ResyncMode, + Resync, mock.MockChainID, false, 0, }, { "sync", - relayer.SyncMode, + Sync, mock.MockChainID, false, mock.LatestBlock.Height, }, { "sync error getting latest block", - relayer.SyncMode, + Sync, big.NewInt(328938), true, 0, }, { "invalidMode", - relayer.Mode("fake"), + SyncMode("fake"), mock.MockChainID, true, 0, @@ -50,7 +49,7 @@ func Test_SetInitialProcessingBlockByMode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - svc, _ := newTestService() + svc, _ := newTestService(tt.mode, FilterAndSubscribe) err := svc.setInitialProcessingBlockByMode( context.Background(), tt.mode, diff --git a/packages/relayer/indexer/filter_then_subscribe_test.go b/packages/relayer/indexer/start_test.go similarity index 53% rename from packages/relayer/indexer/filter_then_subscribe_test.go rename to packages/relayer/indexer/start_test.go index 3d6841d272..4fb1f239f7 100644 --- a/packages/relayer/indexer/filter_then_subscribe_test.go +++ b/packages/relayer/indexer/start_test.go @@ -1,27 +1,21 @@ package indexer import ( - "context" "testing" "time" "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer" "github.com/taikoxyz/taiko-mono/packages/relayer/mock" ) -func Test_FilterThenSubscribe(t *testing.T) { - svc, bridge := newTestService() +func Test_Start(t *testing.T) { + svc, bridge := newTestService(Sync, FilterAndSubscribe) b := bridge.(*mock.Bridge) svc.processingBlockHeight = 0 go func() { - _ = svc.FilterThenSubscribe( - context.Background(), - relayer.Mode(relayer.SyncMode), - relayer.FilterAndSubscribeWatchMode, - ) + _ = svc.Start() }() <-time.After(6 * time.Second) @@ -31,16 +25,12 @@ func Test_FilterThenSubscribe(t *testing.T) { assert.Equal(t, b.ErrorsSent, 2) } -func Test_FilterThenSubscribe_subscribeWatchMode(t *testing.T) { - svc, bridge := newTestService() +func Test_Start_subscribeWatchMode(t *testing.T) { + svc, bridge := newTestService(Sync, Subscribe) b := bridge.(*mock.Bridge) go func() { - _ = svc.FilterThenSubscribe( - context.Background(), - relayer.Mode(relayer.SyncMode), - relayer.SubscribeWatchMode, - ) + _ = svc.Start() }() <-time.After(6 * time.Second) @@ -50,18 +40,14 @@ func Test_FilterThenSubscribe_subscribeWatchMode(t *testing.T) { assert.Equal(t, b.ErrorsSent, 2) } -func Test_FilterThenSubscribe_alreadyCaughtUp(t *testing.T) { - svc, bridge := newTestService() +func Test_Start_alreadyCaughtUp(t *testing.T) { + svc, bridge := newTestService(Sync, FilterAndSubscribe) b := bridge.(*mock.Bridge) svc.processingBlockHeight = mock.LatestBlockNumber.Uint64() go func() { - _ = svc.FilterThenSubscribe( - context.Background(), - relayer.Mode(relayer.SyncMode), - relayer.FilterAndSubscribeWatchMode, - ) + _ = svc.Start() }() <-time.After(6 * time.Second) diff --git a/packages/relayer/indexer/subscribe.go b/packages/relayer/indexer/subscribe.go index decd325d7d..c396915ae8 100644 --- a/packages/relayer/indexer/subscribe.go +++ b/packages/relayer/indexer/subscribe.go @@ -11,18 +11,18 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/pkg/errors" "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) // subscribe subscribes to latest events -func (svc *Service) subscribe(ctx context.Context, chainID *big.Int) error { +func (i *Indexer) subscribe(ctx context.Context, chainID *big.Int) error { slog.Info("subscribing to new events") errChan := make(chan error) - go svc.subscribeMessageSent(ctx, chainID, errChan) + go i.subscribeMessageSent(ctx, chainID, errChan) - go svc.subscribeMessageStatusChanged(ctx, chainID, errChan) + go i.subscribeMessageStatusChanged(ctx, chainID, errChan) // nolint: gosimple for { @@ -38,17 +38,17 @@ func (svc *Service) subscribe(ctx context.Context, chainID *big.Int) error { } } -func (svc *Service) subscribeMessageSent(ctx context.Context, chainID *big.Int, errChan chan error) { +func (i *Indexer) subscribeMessageSent(ctx context.Context, chainID *big.Int, errChan chan error) { sink := make(chan *bridge.BridgeMessageSent) - sub := event.ResubscribeErr(svc.subscriptionBackoff, func(ctx context.Context, err error) (event.Subscription, error) { + sub := event.ResubscribeErr(i.subscriptionBackoff, func(ctx context.Context, err error) (event.Subscription, error) { if err != nil { - slog.Error("svc.bridge.WatchMessageSent", "error", err) + slog.Error("i.bridge.WatchMessageSent", "error", err) } slog.Info("resubscribing to WatchMessageSent events") - return svc.bridge.WatchMessageSent(&bind.WatchOpts{ + return i.bridge.WatchMessageSent(&bind.WatchOpts{ Context: ctx, }, sink, nil) }) @@ -65,28 +65,28 @@ func (svc *Service) subscribeMessageSent(ctx context.Context, chainID *big.Int, case event := <-sink: go func() { slog.Info("new message sent event", "msgHash", common.Hash(event.MsgHash).Hex(), "chainID", chainID.String()) - err := svc.handleEvent(ctx, chainID, event) + err := i.handleEvent(ctx, chainID, event) if err != nil { - slog.Error("svc.subscribe, svc.handleEvent", "error", err) + slog.Error("i.subscribe, i.handleEvent", "error", err) return } - block, err := svc.blockRepo.GetLatestBlockProcessedForEvent(relayer.EventNameMessageSent, chainID) + block, err := i.blockRepo.GetLatestBlockProcessedForEvent(relayer.EventNameMessageSent, chainID) if err != nil { - slog.Error("svc.subscribe, blockRepo.GetLatestBlockProcessedForEvent", "error", err) + slog.Error("i.subscribe, blockRepo.GetLatestBlockProcessedForEvent", "error", err) return } if block.Height < event.Raw.BlockNumber { - err = svc.blockRepo.Save(relayer.SaveBlockOpts{ + err = i.blockRepo.Save(relayer.SaveBlockOpts{ Height: event.Raw.BlockNumber, Hash: event.Raw.BlockHash, ChainID: chainID, EventName: relayer.EventNameMessageSent, }) if err != nil { - slog.Error("svc.subscribe, svc.blockRepo.Save", "error", err) + slog.Error("i.subscribe, i.blockRepo.Save", "error", err) return } @@ -97,16 +97,16 @@ func (svc *Service) subscribeMessageSent(ctx context.Context, chainID *big.Int, } } -func (svc *Service) subscribeMessageStatusChanged(ctx context.Context, chainID *big.Int, errChan chan error) { +func (i *Indexer) subscribeMessageStatusChanged(ctx context.Context, chainID *big.Int, errChan chan error) { sink := make(chan *bridge.BridgeMessageStatusChanged) - sub := event.ResubscribeErr(svc.subscriptionBackoff, func(ctx context.Context, err error) (event.Subscription, error) { + sub := event.ResubscribeErr(i.subscriptionBackoff, func(ctx context.Context, err error) (event.Subscription, error) { if err != nil { - slog.Error("svc.bridge.WatchMessageStatusChanged", "error", err) + slog.Error("i.bridge.WatchMessageStatusChanged", "error", err) } slog.Info("resubscribing to WatchMessageStatusChanged events") - return svc.bridge.WatchMessageStatusChanged(&bind.WatchOpts{ + return i.bridge.WatchMessageStatusChanged(&bind.WatchOpts{ Context: ctx, }, sink, nil) }) @@ -126,8 +126,8 @@ func (svc *Service) subscribeMessageStatusChanged(ctx context.Context, chainID * "chainID", chainID.String(), ) - if err := svc.saveMessageStatusChangedEvent(ctx, chainID, event); err != nil { - slog.Error("svc.subscribe, svc.saveMessageStatusChangedEvent", "error", err) + if err := i.saveMessageStatusChangedEvent(ctx, chainID, event); err != nil { + slog.Error("i.subscribe, i.saveMessageStatusChangedEvent", "error", err) } } } diff --git a/packages/relayer/indexer/subscribe_test.go b/packages/relayer/indexer/subscribe_test.go index a606fe53cd..9c9705c6f1 100644 --- a/packages/relayer/indexer/subscribe_test.go +++ b/packages/relayer/indexer/subscribe_test.go @@ -10,7 +10,7 @@ import ( ) func Test_subscribe(t *testing.T) { - svc, bridge := newTestService() + svc, bridge := newTestService(Sync, Subscribe) go func() { _ = svc.subscribe(context.Background(), mock.MockChainID) diff --git a/packages/relayer/message/process_message_test.go b/packages/relayer/message/process_message_test.go deleted file mode 100644 index d855f05190..0000000000 --- a/packages/relayer/message/process_message_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package message - -import ( - "context" - "math/big" - "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" - "github.com/taikoxyz/taiko-mono/packages/relayer/mock" -) - -func Test_sendProcessMessageCall(t *testing.T) { - p := newTestProcessor(true) - - _, err := p.sendProcessMessageCall( - context.Background(), - &bridge.BridgeMessageSent{ - Message: bridge.IBridgeMessage{ - DestChainId: mock.MockChainID, - Fee: new(big.Int).Add(mock.ProcessMessageTx.Cost(), big.NewInt(1)), - }, - }, []byte{}) - - assert.Nil(t, err) - - assert.Equal(t, p.destNonce, mock.PendingNonce) -} - -func Test_ProcessMessage_messageNotReceived(t *testing.T) { - p := newTestProcessor(true) - - err := p.ProcessMessage(context.Background(), &bridge.BridgeMessageSent{ - Message: bridge.IBridgeMessage{ - GasLimit: big.NewInt(1), - }, - }, &relayer.Event{}) - assert.EqualError(t, err, "message not received") -} - -func Test_ProcessMessage_gasLimit0(t *testing.T) { - p := newTestProcessor(true) - - err := p.ProcessMessage(context.Background(), &bridge.BridgeMessageSent{}, &relayer.Event{}) - assert.EqualError(t, errors.New("only user can process this, gasLimit set to 0"), err.Error()) -} - -func Test_ProcessMessage_noChainId(t *testing.T) { - p := newTestProcessor(true) - - err := p.ProcessMessage(context.Background(), &bridge.BridgeMessageSent{ - Message: bridge.IBridgeMessage{ - GasLimit: big.NewInt(1), - }, - MsgHash: mock.SuccessMsgHash, - }, &relayer.Event{}) - assert.EqualError(t, err, "p.sendProcessMessageCall: bind.NewKeyedTransactorWithChainID: no chain id specified") -} - -func Test_ProcessMessage(t *testing.T) { - p := newTestProcessor(true) - - err := p.ProcessMessage(context.Background(), &bridge.BridgeMessageSent{ - Message: bridge.IBridgeMessage{ - GasLimit: big.NewInt(1), - DestChainId: mock.MockChainID, - Fee: big.NewInt(1000000000), - SrcChainId: mock.MockChainID, - }, - MsgHash: mock.SuccessMsgHash, - }, &relayer.Event{}) - - assert.Nil( - t, - err, - ) -} - -// func Test_ProcessMessage_unprofitable(t *testing.T) { -// p := newTestProcessor(true) - -// err := p.ProcessMessage(context.Background(), &bridge.BridgeMessageSent{ -// Message: bridge.IBridgeMessage{ -// GasLimit: big.NewInt(1), -// DestChainId: mock.MockChainID, -// }, -// Signal: mock.SuccessMsgHash, -// }, &relayer.Event{}) - -// assert.EqualError( -// t, -// err, -// "p.sendProcessMessageCall: "+relayer.ErrUnprofitable.Error(), -// ) -// } diff --git a/packages/relayer/message/processor.go b/packages/relayer/message/processor.go deleted file mode 100644 index 4fe00c26f8..0000000000 --- a/packages/relayer/message/processor.go +++ /dev/null @@ -1,140 +0,0 @@ -package message - -import ( - "context" - "crypto/ecdsa" - "math/big" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/proof" -) - -type ethClient interface { - PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) - TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) - BlockNumber(ctx context.Context) (uint64, error) - HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) - SuggestGasPrice(ctx context.Context) (*big.Int, error) - SuggestGasTipCap(ctx context.Context) (*big.Int, error) -} - -type Processor struct { - eventRepo relayer.EventRepository - srcEthClient ethClient - destEthClient ethClient - rpc relayer.Caller - ecdsaKey *ecdsa.PrivateKey - - destBridge relayer.Bridge - destHeaderSyncer relayer.HeaderSyncer - destERC20Vault relayer.TokenVault - destERC1155Vault relayer.TokenVault - destERC721Vault relayer.TokenVault - - prover *proof.Prover - - mu *sync.Mutex - - destNonce uint64 - relayerAddr common.Address - srcSignalServiceAddress common.Address - confirmations uint64 - - profitableOnly relayer.ProfitableOnly - headerSyncIntervalSeconds int64 - - confTimeoutInSeconds int64 -} - -type NewProcessorOpts struct { - Prover *proof.Prover - ECDSAKey *ecdsa.PrivateKey - RPCClient relayer.Caller - SrcETHClient ethClient - DestETHClient ethClient - DestBridge relayer.Bridge - EventRepo relayer.EventRepository - DestHeaderSyncer relayer.HeaderSyncer - DestERC20Vault relayer.TokenVault - DestERC721Vault relayer.TokenVault - DestERC1155Vault relayer.TokenVault - RelayerAddress common.Address - SrcSignalServiceAddress common.Address - Confirmations uint64 - ProfitableOnly relayer.ProfitableOnly - HeaderSyncIntervalSeconds int64 - ConfirmationsTimeoutInSeconds int64 -} - -func NewProcessor(opts NewProcessorOpts) (*Processor, error) { - if opts.Prover == nil { - return nil, relayer.ErrNoProver - } - - if opts.ECDSAKey == nil { - return nil, relayer.ErrNoECDSAKey - } - - if opts.RPCClient == nil { - return nil, relayer.ErrNoRPCClient - } - - if opts.DestETHClient == nil { - return nil, relayer.ErrNoEthClient - } - - if opts.SrcETHClient == nil { - return nil, relayer.ErrNoEthClient - } - - if opts.DestBridge == nil { - return nil, relayer.ErrNoBridge - } - - if opts.EventRepo == nil { - return nil, relayer.ErrNoEventRepository - } - - if opts.DestHeaderSyncer == nil { - return nil, relayer.ErrNoTaikoL2 - } - - if opts.Confirmations == 0 { - return nil, relayer.ErrInvalidConfirmations - } - - if opts.ConfirmationsTimeoutInSeconds == 0 { - return nil, relayer.ErrInvalidConfirmationsTimeoutInSeconds - } - - return &Processor{ - eventRepo: opts.EventRepo, - prover: opts.Prover, - ecdsaKey: opts.ECDSAKey, - rpc: opts.RPCClient, - - srcEthClient: opts.SrcETHClient, - - destEthClient: opts.DestETHClient, - destBridge: opts.DestBridge, - destHeaderSyncer: opts.DestHeaderSyncer, - destERC20Vault: opts.DestERC20Vault, - destERC721Vault: opts.DestERC1155Vault, - destERC1155Vault: opts.DestERC721Vault, - - mu: &sync.Mutex{}, - - destNonce: 0, - relayerAddr: opts.RelayerAddress, - srcSignalServiceAddress: opts.SrcSignalServiceAddress, - confirmations: opts.Confirmations, - - profitableOnly: opts.ProfitableOnly, - headerSyncIntervalSeconds: opts.HeaderSyncIntervalSeconds, - confTimeoutInSeconds: opts.ConfirmationsTimeoutInSeconds, - }, nil -} diff --git a/packages/relayer/message/processor_test.go b/packages/relayer/message/processor_test.go deleted file mode 100644 index 6709ef6ab4..0000000000 --- a/packages/relayer/message/processor_test.go +++ /dev/null @@ -1,226 +0,0 @@ -package message - -import ( - "crypto/ecdsa" - "sync" - "testing" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" - "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/icrosschainsync" - "github.com/taikoxyz/taiko-mono/packages/relayer/mock" - "github.com/taikoxyz/taiko-mono/packages/relayer/proof" - "github.com/taikoxyz/taiko-mono/packages/relayer/repo" - "gopkg.in/go-playground/assert.v1" -) - -var dummyEcdsaKey = "8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f" - -func newTestProcessor(profitableOnly relayer.ProfitableOnly) *Processor { - privateKey, _ := crypto.HexToECDSA(dummyEcdsaKey) - - prover, _ := proof.New( - &mock.Blocker{}, - ) - - return &Processor{ - eventRepo: &mock.EventRepository{}, - destBridge: &mock.Bridge{}, - srcEthClient: &mock.EthClient{}, - destEthClient: &mock.EthClient{}, - destERC20Vault: &mock.TokenVault{}, - mu: &sync.Mutex{}, - ecdsaKey: privateKey, - destHeaderSyncer: &mock.HeaderSyncer{}, - prover: prover, - rpc: &mock.Caller{}, - profitableOnly: profitableOnly, - headerSyncIntervalSeconds: 1, - confTimeoutInSeconds: 900, - } -} -func Test_NewProcessor(t *testing.T) { - tests := []struct { - name string - opts NewProcessorOpts - wantErr error - }{ - { - "success", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - nil, - }, - { - "errNoConfirmationsTimeoutInSeconds", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - }, - relayer.ErrInvalidConfirmationsTimeoutInSeconds, - }, - { - "errNoConfirmations", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrInvalidConfirmations, - }, - { - "errNoSrcClient", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoEthClient, - }, - { - "errNoProver", - NewProcessorOpts{ - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - Confirmations: 1, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoProver, - }, - { - "errNoECDSAKey", - NewProcessorOpts{ - Prover: &proof.Prover{}, - - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoECDSAKey, - }, - { - "noRpcClient", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoRPCClient, - }, - { - "noDestEthClient", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoEthClient, - }, - { - "errNoDestBridge", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - EventRepo: &repo.EventRepository{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoBridge, - }, - { - "errNoEventRepo", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - DestBridge: &bridge.Bridge{}, - DestHeaderSyncer: &icrosschainsync.ICrossChainSync{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoEventRepository, - }, - { - "errNoTaikoL2", - NewProcessorOpts{ - Prover: &proof.Prover{}, - ECDSAKey: &ecdsa.PrivateKey{}, - RPCClient: &rpc.Client{}, - SrcETHClient: ðclient.Client{}, - DestETHClient: ðclient.Client{}, - EventRepo: &repo.EventRepository{}, - DestBridge: &bridge.Bridge{}, - Confirmations: 1, - ConfirmationsTimeoutInSeconds: 900, - }, - relayer.ErrNoTaikoL2, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := NewProcessor(tt.opts) - assert.Equal(t, tt.wantErr, err) - }) - } -} diff --git a/packages/relayer/metrics/metrics.go b/packages/relayer/metrics/metrics.go new file mode 100644 index 0000000000..efb69e26f1 --- /dev/null +++ b/packages/relayer/metrics/metrics.go @@ -0,0 +1,34 @@ +package metrics + +import ( + "context" + "fmt" + + echoprom "github.com/labstack/echo-contrib/prometheus" + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/urfave/cli/v2" + "golang.org/x/exp/slog" +) + +// Serve starts the metrics server on the given address, will be closed when the given +// context is cancelled. +func Serve(ctx context.Context, c *cli.Context) (*echo.Echo, func() error) { + // Enable metrics middleware + p := echoprom.NewPrometheus("echo", nil) + e := echo.New() + p.SetMetricsPath(e) + + go func() { + <-ctx.Done() + + if err := e.Shutdown(ctx); err != nil { + log.Error("Failed to close metrics server", "error", err) + } + }() + + slog.Info("Starting metrics server", "port", c.Uint64(flags.MetricsHTTPPort.Name)) + + return e, func() error { return e.Start(fmt.Sprintf(":%v", c.Uint64(flags.MetricsHTTPPort.Name))) } +} diff --git a/packages/relayer/metrics/metrics_test.go b/packages/relayer/metrics/metrics_test.go new file mode 100644 index 0000000000..5c4775e2e9 --- /dev/null +++ b/packages/relayer/metrics/metrics_test.go @@ -0,0 +1,64 @@ +package metrics + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/urfave/cli/v2" +) + +func Test_Metrics(t *testing.T) { + app := cli.NewApp() + app.Flags = []cli.Flag{ + flags.MetricsHTTPPort, + } + + app.Action = func(c *cli.Context) error { + ctx, cancel := context.WithCancel(context.Background()) + + var e *echo.Echo + + var startFunc func() error + + var err error + + go func() { + e, startFunc = Serve(ctx, c) + + err = startFunc() + }() + + for e == nil && err == nil { + time.Sleep(1 * time.Second) + } + + assert.Nil(t, err) + assert.NotNil(t, e) + + req, _ := http.NewRequest(echo.GET, "/metrics", nil) + rec := httptest.NewRecorder() + + e.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("Test_Metrics expected code %v, got %v", http.StatusOK, rec.Code) + } + + cancel() + + assert.Nil(t, err) + + return nil + } + + assert.Nil(t, app.Run([]string{ + "TestMetrics", + "-" + flags.MetricsHTTPPort.Name, "5019", + })) +} diff --git a/packages/relayer/mock/bridge.go b/packages/relayer/mock/bridge.go index de0c288675..245536be57 100644 --- a/packages/relayer/mock/bridge.go +++ b/packages/relayer/mock/bridge.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) var ( diff --git a/packages/relayer/mock/db.go b/packages/relayer/mock/db.go new file mode 100644 index 0000000000..00d2111641 --- /dev/null +++ b/packages/relayer/mock/db.go @@ -0,0 +1,18 @@ +package mock + +import ( + "database/sql" + + "gorm.io/gorm" +) + +type DB struct { +} + +func (db *DB) DB() (*sql.DB, error) { + return &sql.DB{}, nil +} + +func (db *DB) GormDB() *gorm.DB { + return &gorm.DB{} +} diff --git a/packages/relayer/mock/queue.go b/packages/relayer/mock/queue.go new file mode 100644 index 0000000000..4e775cf081 --- /dev/null +++ b/packages/relayer/mock/queue.go @@ -0,0 +1,39 @@ +package mock + +import ( + "context" + "sync" + + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" +) + +type Queue struct { +} + +func (r *Queue) Start(ctx context.Context, queueName string) error { + return nil +} + +func (r *Queue) Close(ctx context.Context) { + +} + +func (r *Queue) Notify(ctx context.Context, wg *sync.WaitGroup) error { + return nil +} + +func (r *Queue) Publish(ctx context.Context, msg []byte) error { + return nil +} + +func (r *Queue) Ack(ctx context.Context, msg queue.Message) error { + return nil +} + +func (r *Queue) Nack(ctx context.Context, msg queue.Message) error { + return nil +} + +func (r *Queue) Subscribe(ctx context.Context, msgChan chan<- queue.Message, wg *sync.WaitGroup) error { + return nil +} diff --git a/packages/relayer/processor/can_process_message.go b/packages/relayer/processor/can_process_message.go new file mode 100644 index 0000000000..2c2486c19f --- /dev/null +++ b/packages/relayer/processor/can_process_message.go @@ -0,0 +1,34 @@ +package processor + +import ( + "context" + "log/slog" + + "github.com/ethereum/go-ethereum/common" + "github.com/taikoxyz/taiko-mono/packages/relayer" +) + +func canProcessMessage( + ctx context.Context, + eventStatus relayer.EventStatus, + messageOwner common.Address, + relayerAddress common.Address, +) bool { + // we can not process, exit early + if eventStatus == relayer.EventStatusNewOnlyOwner { + if messageOwner != relayerAddress { + slog.Info("gasLimit == 0 and owner is not the current relayer key, can not process. continuing loop") + return false + } + + return true + } + + if eventStatus == relayer.EventStatusNew { + return true + } + + slog.Info("cant process message", "eventStatus", eventStatus.String()) + + return false +} diff --git a/packages/relayer/processor/can_process_message_test.go b/packages/relayer/processor/can_process_message_test.go new file mode 100644 index 0000000000..c9a9cf59f7 --- /dev/null +++ b/packages/relayer/processor/can_process_message_test.go @@ -0,0 +1,80 @@ +package processor + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/taikoxyz/taiko-mono/packages/relayer" +) + +var ( + relayerAddr = common.HexToAddress("0x71C7656EC7ab88b098defB751B7401B5f6d8976F") +) + +func Test_canProcessMessage(t *testing.T) { + tests := []struct { + name string + eventStatus relayer.EventStatus + messageOwner common.Address + relayerAddress common.Address + want bool + }{ + { + "canProcess, eventStatusNew", + relayer.EventStatusNew, + relayerAddr, + relayerAddr, + true, + }, + { + "cantProcess, eventStatusDone", + relayer.EventStatusDone, + relayerAddr, + relayerAddr, + false, + }, + { + "cantProcess, eventStatusRetriable", + relayer.EventStatusRetriable, + relayerAddr, + relayerAddr, + false, + }, + { + "cantProcess, eventStatusNewOnlyOwner and relayer is not owner", + relayer.EventStatusNewOnlyOwner, + common.HexToAddress("0x"), + relayerAddr, + false, + }, + { + "cantProcess, eventStatusFailed", + relayer.EventStatusFailed, + common.HexToAddress("0x"), + relayerAddr, + false, + }, + { + "canProcess, eventStatusOnlyOwner and relayer address is owner", + relayer.EventStatusNewOnlyOwner, + relayerAddr, + relayerAddr, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + canProcess := canProcessMessage( + context.Background(), + tt.eventStatus, + tt.messageOwner, + tt.relayerAddress, + ) + + assert.Equal(t, tt.want, canProcess) + }) + } +} diff --git a/packages/relayer/processor/config.go b/packages/relayer/processor/config.go new file mode 100644 index 0000000000..a7edc96f15 --- /dev/null +++ b/packages/relayer/processor/config.go @@ -0,0 +1,139 @@ +package processor + +import ( + "crypto/ecdsa" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/taikoxyz/taiko-mono/packages/relayer/db" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue/rabbitmq" + "github.com/urfave/cli/v2" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +type Config struct { + // address configs + SrcSignalServiceAddress common.Address + DestBridgeAddress common.Address + DestERC721VaultAddress common.Address + DestERC20VaultAddress common.Address + DestERC1155VaultAddress common.Address + DestTaikoAddress common.Address + + // private key + ProcessorPrivateKey *ecdsa.PrivateKey + + // processing configs + HeaderSyncInterval uint64 + Confirmations uint64 + ConfirmationsTimeout uint64 + ProfitableOnly bool + + // backoff configs + BackoffRetryInterval uint64 + BackOffMaxRetrys uint64 + + // db configs + DatabaseUsername string + DatabasePassword string + DatabaseName string + DatabaseHost string + DatabaseMaxIdleConns uint64 + DatabaseMaxOpenConns uint64 + DatabaseMaxConnLifetime uint64 + // queue configs + QueueUsername string + QueuePassword string + QueueHost string + QueuePort uint64 + // rpc configs + SrcRPCUrl string + DestRPCUrl string + ETHClientTimeout uint64 + CORSOrigins []string + OpenQueueFunc func() (queue.Queue, error) + OpenDBFunc func() (DB, error) +} + +// NewConfigFromCliContext creates a new config instance from command line flags. +func NewConfigFromCliContext(c *cli.Context) (*Config, error) { + processorPrivateKey, err := crypto.ToECDSA( + common.Hex2Bytes(c.String(flags.ProcessorPrivateKey.Name)), + ) + if err != nil { + return nil, fmt.Errorf("invalid processorPrivateKey: %w", err) + } + + return &Config{ + ProcessorPrivateKey: processorPrivateKey, + SrcSignalServiceAddress: common.HexToAddress(c.String(flags.SrcSignalServiceAddress.Name)), + DestTaikoAddress: common.HexToAddress(c.String(flags.DestTaikoAddress.Name)), + DestBridgeAddress: common.HexToAddress(c.String(flags.DestBridgeAddress.Name)), + DestERC721VaultAddress: common.HexToAddress(c.String(flags.DestERC721VaultAddress.Name)), + DestERC20VaultAddress: common.HexToAddress(c.String(flags.DestERC20VaultAddress.Name)), + DestERC1155VaultAddress: common.HexToAddress(c.String(flags.DestERC1155VaultAddress.Name)), + DatabaseUsername: c.String(flags.DatabaseUsername.Name), + DatabasePassword: c.String(flags.DatabasePassword.Name), + DatabaseName: c.String(flags.DatabaseName.Name), + DatabaseHost: c.String(flags.DatabaseHost.Name), + DatabaseMaxIdleConns: c.Uint64(flags.DatabaseMaxIdleConns.Name), + DatabaseMaxOpenConns: c.Uint64(flags.DatabaseMaxOpenConns.Name), + DatabaseMaxConnLifetime: c.Uint64(flags.DatabaseConnMaxLifetime.Name), + QueueUsername: c.String(flags.QueueUsername.Name), + QueuePassword: c.String(flags.QueuePassword.Name), + QueuePort: c.Uint64(flags.QueuePort.Name), + QueueHost: c.String(flags.QueueHost.Name), + SrcRPCUrl: c.String(flags.SrcRPCUrl.Name), + DestRPCUrl: c.String(flags.DestRPCUrl.Name), + CORSOrigins: strings.Split(c.String(flags.CORSOrigins.Name), ","), + HeaderSyncInterval: c.Uint64(flags.HeaderSyncInterval.Name), + Confirmations: c.Uint64(flags.Confirmations.Name), + ConfirmationsTimeout: c.Uint64(flags.ConfirmationTimeout.Name), + ProfitableOnly: c.Bool(flags.ProfitableOnly.Name), + BackoffRetryInterval: c.Uint64(flags.BackOffRetryInterval.Name), + BackOffMaxRetrys: c.Uint64(flags.BackOffMaxRetrys.Name), + ETHClientTimeout: c.Uint64(flags.ETHClientTimeout.Name), + OpenDBFunc: func() (DB, error) { + return db.OpenDBConnection(db.DBConnectionOpts{ + Name: c.String(flags.DatabaseUsername.Name), + Password: c.String(flags.DatabasePassword.Name), + Database: c.String(flags.DatabaseName.Name), + Host: c.String(flags.DatabaseHost.Name), + MaxIdleConns: c.Uint64(flags.DatabaseMaxIdleConns.Name), + MaxOpenConns: c.Uint64(flags.DatabaseMaxOpenConns.Name), + MaxConnLifetime: c.Uint64(flags.DatabaseConnMaxLifetime.Name), + OpenFunc: func(dsn string) (*db.DB, error) { + gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + return nil, err + } + + return db.New(gormDB), nil + }, + }) + }, + OpenQueueFunc: func() (queue.Queue, error) { + opts := queue.NewQueueOpts{ + Username: c.String(flags.QueueUsername.Name), + Password: c.String(flags.QueuePassword.Name), + Host: c.String(flags.QueueHost.Name), + Port: c.String(flags.QueuePort.Name), + } + + q, err := rabbitmq.NewQueue(opts) + if err != nil { + return nil, err + } + + return q, nil + }, + }, nil +} diff --git a/packages/relayer/processor/config_test.go b/packages/relayer/processor/config_test.go new file mode 100644 index 0000000000..0ee7777487 --- /dev/null +++ b/packages/relayer/processor/config_test.go @@ -0,0 +1,85 @@ +package processor + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" + "github.com/taikoxyz/taiko-mono/packages/relayer/mock" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" + "github.com/urfave/cli/v2" +) + +var ( + destBridgeAddr = "0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377" +) + +func setupApp() *cli.App { + app := cli.NewApp() + app.Flags = flags.ProcessorFlags + app.Action = func(ctx *cli.Context) error { + _, err := NewConfigFromCliContext(ctx) + return err + } + + return app +} + +func TestNewConfigFromCliContext(t *testing.T) { + app := setupApp() + + app.Action = func(ctx *cli.Context) error { + c, err := NewConfigFromCliContext(ctx) + assert.Nil(t, err) + assert.Equal(t, "dbuser", c.DatabaseUsername) + assert.Equal(t, "dbpass", c.DatabasePassword) + assert.Equal(t, "dbname", c.DatabaseName) + assert.Equal(t, "dbhost", c.DatabaseHost) + assert.Equal(t, "queuename", c.QueueUsername) + assert.Equal(t, "queuepassword", c.QueuePassword) + assert.Equal(t, "queuehost", c.QueueHost) + assert.Equal(t, uint64(5555), c.QueuePort) + assert.Equal(t, "srcRpcUrl", c.SrcRPCUrl) + assert.Equal(t, "destRpcUrl", c.DestRPCUrl) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.DestBridgeAddress) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.SrcSignalServiceAddress) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.DestERC20VaultAddress) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.DestERC721VaultAddress) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.DestERC1155VaultAddress) + assert.Equal(t, common.HexToAddress(destBridgeAddr), c.DestTaikoAddress) + + c.OpenDBFunc = func() (DB, error) { + return &mock.DB{}, nil + } + + c.OpenQueueFunc = func() (queue.Queue, error) { + return &mock.Queue{}, nil + } + + // assert.Nil(t, InitFromConfig(context.Background(), new(Processor), c)) + + return err + } + + assert.Nil(t, app.Run([]string{ + "TestNewConfigFromCliContext", + "-" + flags.DatabaseUsername.Name, "dbuser", + "-" + flags.DatabasePassword.Name, "dbpass", + "-" + flags.DatabaseHost.Name, "dbhost", + "-" + flags.DatabaseName.Name, "dbname", + "-" + flags.QueueUsername.Name, "queuename", + "-" + flags.QueuePassword.Name, "queuepassword", + "-" + flags.QueueHost.Name, "queuehost", + "-" + flags.QueuePort.Name, "5555", + "-" + flags.SrcRPCUrl.Name, "srcRpcUrl", + "-" + flags.DestRPCUrl.Name, "destRpcUrl", + "-" + flags.DestBridgeAddress.Name, destBridgeAddr, + "-" + flags.SrcSignalServiceAddress.Name, destBridgeAddr, + "-" + flags.DestERC721VaultAddress.Name, destBridgeAddr, + "-" + flags.DestERC20VaultAddress.Name, destBridgeAddr, + "-" + flags.DestERC1155VaultAddress.Name, destBridgeAddr, + "-" + flags.DestTaikoAddress.Name, destBridgeAddr, + "-" + flags.ProcessorPrivateKey.Name, dummyEcdsaKey, + })) +} diff --git a/packages/relayer/message/errors.go b/packages/relayer/processor/errors.go similarity index 97% rename from packages/relayer/message/errors.go rename to packages/relayer/processor/errors.go index 0bf2d406ae..7b4fb23064 100644 --- a/packages/relayer/message/errors.go +++ b/packages/relayer/processor/errors.go @@ -1,4 +1,4 @@ -package message +package processor import ( "math/big" diff --git a/packages/relayer/message/estimate_gas.go b/packages/relayer/processor/estimate_gas.go similarity index 89% rename from packages/relayer/message/estimate_gas.go rename to packages/relayer/processor/estimate_gas.go index bfb59609e9..f5bf9793a1 100644 --- a/packages/relayer/message/estimate_gas.go +++ b/packages/relayer/processor/estimate_gas.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/pkg/errors" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) func (p *Processor) estimateGas( diff --git a/packages/relayer/message/get_latest_nonce.go b/packages/relayer/processor/get_latest_nonce.go similarity index 95% rename from packages/relayer/message/get_latest_nonce.go rename to packages/relayer/processor/get_latest_nonce.go index 8dbba0bdde..6bf10c5715 100644 --- a/packages/relayer/message/get_latest_nonce.go +++ b/packages/relayer/processor/get_latest_nonce.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" diff --git a/packages/relayer/message/get_latest_nonce_test.go b/packages/relayer/processor/get_latest_nonce_test.go similarity index 95% rename from packages/relayer/message/get_latest_nonce_test.go rename to packages/relayer/processor/get_latest_nonce_test.go index 720ddbd316..cea74de61f 100644 --- a/packages/relayer/message/get_latest_nonce_test.go +++ b/packages/relayer/processor/get_latest_nonce_test.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" diff --git a/packages/relayer/message/is_profitable.go b/packages/relayer/processor/is_profitable.go similarity index 86% rename from packages/relayer/message/is_profitable.go rename to packages/relayer/processor/is_profitable.go index 02a6a18549..e12ec3a0c5 100644 --- a/packages/relayer/message/is_profitable.go +++ b/packages/relayer/processor/is_profitable.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" @@ -6,7 +6,7 @@ import ( "log/slog" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) func (p *Processor) isProfitable( diff --git a/packages/relayer/message/is_profitable_test.go b/packages/relayer/processor/is_profitable_test.go similarity index 93% rename from packages/relayer/message/is_profitable_test.go rename to packages/relayer/processor/is_profitable_test.go index 1b4d64d711..4c137fe6f2 100644 --- a/packages/relayer/message/is_profitable_test.go +++ b/packages/relayer/processor/is_profitable_test.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" @@ -6,7 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" "github.com/taikoxyz/taiko-mono/packages/relayer/mock" ) diff --git a/packages/relayer/message/process_message.go b/packages/relayer/processor/process_message.go similarity index 75% rename from packages/relayer/message/process_message.go rename to packages/relayer/processor/process_message.go index bb52113778..48a322cd6c 100644 --- a/packages/relayer/message/process_message.go +++ b/packages/relayer/processor/process_message.go @@ -1,14 +1,16 @@ -package message +package processor import ( "context" "encoding/hex" + "encoding/json" "fmt" "log/slog" "math/big" "strings" "time" + "github.com/cenkalti/backoff" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -16,28 +18,75 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/pkg/errors" "github.com/taikoxyz/taiko-mono/packages/relayer" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" ) +var ( + errUnprocessable = errors.New("message is unprocessable") +) + +func (p *Processor) eventStatusFromMsgHash( + ctx context.Context, + gasLimit *big.Int, + signal [32]byte, +) (relayer.EventStatus, error) { + var eventStatus relayer.EventStatus + + ctx, cancel := context.WithTimeout(ctx, p.ethClientTimeout) + + defer cancel() + + messageStatus, err := p.destBridge.GetMessageStatus(&bind.CallOpts{ + Context: ctx, + }, signal) + if err != nil { + return 0, errors.Wrap(err, "svc.destBridge.GetMessageStatus") + } + + eventStatus = relayer.EventStatus(messageStatus) + if eventStatus == relayer.EventStatusNew { + if gasLimit == nil || gasLimit.Cmp(common.Big0) == 0 { + // if gasLimit is 0, relayer can not process this. + eventStatus = relayer.EventStatusNewOnlyOwner + } + } + + return eventStatus, nil +} + // Process prepares and calls `processMessage` on the bridge. // the proof must be generated from the gethclient's eth_getProof via the Prover, // then rlp-encoded and combined as a singular byte slice, // then abi encoded into a SignalProof struct as the contract // expects -func (p *Processor) ProcessMessage( +func (p *Processor) processMessage( ctx context.Context, - event *bridge.BridgeMessageSent, - e *relayer.Event, + msg queue.Message, ) error { - if event.Message.GasLimit == nil || event.Message.GasLimit.Cmp(common.Big0) == 0 { + msgBody := &queue.QueueMessageBody{} + if err := json.Unmarshal(msg.Body, msgBody); err != nil { + return errors.Wrap(err, "json.Unmarshal") + } + + if msgBody.Event.Message.GasLimit == nil || msgBody.Event.Message.GasLimit.Cmp(common.Big0) == 0 { return errors.New("only user can process this, gasLimit set to 0") } - if err := p.waitForConfirmations(ctx, event.Raw.TxHash, event.Raw.BlockNumber); err != nil { + eventStatus, err := p.eventStatusFromMsgHash(ctx, msgBody.Event.Message.GasLimit, msgBody.Event.MsgHash) + if err != nil { + return errors.Wrap(err, "p.eventStatusFromMsgHash") + } + + if !canProcessMessage(ctx, eventStatus, msgBody.Event.Message.User, p.relayerAddr) { + return errUnprocessable + } + + if err := p.waitForConfirmations(ctx, msgBody.Event.Raw.TxHash, msgBody.Event.Raw.BlockNumber); err != nil { return errors.Wrap(err, "p.waitForConfirmations") } - if err := p.waitHeaderSynced(ctx, event); err != nil { + if err := p.waitHeaderSynced(ctx, msgBody.Event); err != nil { return errors.Wrap(err, "p.waitHeaderSynced") } @@ -49,8 +98,8 @@ func (p *Processor) ProcessMessage( } hashed := crypto.Keccak256( - event.Raw.Address.Bytes(), - event.MsgHash[:], + msgBody.Event.Raw.Address.Bytes(), + msgBody.Event.MsgHash[:], ) key := hex.EncodeToString(hashed) @@ -58,11 +107,11 @@ func (p *Processor) ProcessMessage( encodedSignalProof, err := p.prover.EncodedSignalProof(ctx, p.rpc, p.srcSignalServiceAddress, key, latestSyncedHeader) if err != nil { slog.Error("srcChainID: %v, destChainID: %v, txHash: %v: msgHash: %v, from: %v encountered signalProofError %v", - event.Message.SrcChainId.String(), - event.Message.DestChainId.String(), - event.Raw.TxHash.Hex(), - common.Hash(event.MsgHash).Hex(), - event.Message.User.Hex(), + msgBody.Event.Message.SrcChainId.String(), + msgBody.Event.Message.DestChainId.String(), + msgBody.Event.Raw.TxHash.Hex(), + common.Hash(msgBody.Event.MsgHash).Hex(), + msgBody.Event.Message.User.Hex(), err, ) @@ -74,7 +123,7 @@ func (p *Processor) ProcessMessage( // an issue with the signal generation. received, err := p.destBridge.IsMessageReceived(&bind.CallOpts{ Context: ctx, - }, event.MsgHash, event.Message.SrcChainId, encodedSignalProof) + }, msgBody.Event.MsgHash, msgBody.Event.Message.SrcChainId, encodedSignalProof) if err != nil { return errors.Wrap(err, "p.destBridge.IsMessageReceived") } @@ -82,8 +131,8 @@ func (p *Processor) ProcessMessage( // message will fail when we try to process it if !received { slog.Warn("Message not received on dest chain", - "msgHash", common.Hash(event.MsgHash).Hex(), - "srcChainId", event.Message.SrcChainId.String(), + "msgHash", common.Hash(msgBody.Event.MsgHash).Hex(), + "srcChainId", msgBody.Event.Message.SrcChainId.String(), ) relayer.MessagesNotReceivedOnDestChain.Inc() @@ -91,9 +140,26 @@ func (p *Processor) ProcessMessage( return errors.New("message not received") } - tx, err := p.sendProcessMessageCall(ctx, event, encodedSignalProof) - if err != nil { - return errors.Wrap(err, "p.sendProcessMessageCall") + var tx *types.Transaction + + sendTx := func() error { + if ctx.Err() != nil { + return nil + } + + tx, err = p.sendProcessMessageCall(ctx, msgBody.Event, encodedSignalProof) + if err != nil { + return err + } + + return nil + } + + if err := backoff.Retry(sendTx, backoff.WithMaxRetries( + backoff.NewConstantBackOff(p.backOffRetryInterval), + p.backOffMaxRetries), + ); err != nil { + return err } relayer.EventsProcessed.Inc() @@ -107,13 +173,13 @@ func (p *Processor) ProcessMessage( return errors.Wrap(err, "relayer.WaitReceipt") } - if err := p.saveMessageStatusChangedEvent(ctx, receipt, e, event); err != nil { + if err := p.saveMessageStatusChangedEvent(ctx, receipt, msgBody.Event); err != nil { return errors.Wrap(err, "p.saveMEssageStatusChangedEvent") } slog.Info("Mined tx", "txHash", hex.EncodeToString(tx.Hash().Bytes())) - messageStatus, err := p.destBridge.GetMessageStatus(&bind.CallOpts{}, event.MsgHash) + messageStatus, err := p.destBridge.GetMessageStatus(&bind.CallOpts{}, msgBody.Event.MsgHash) if err != nil { return errors.Wrap(err, "p.destBridge.GetMessageStatus") } @@ -121,7 +187,7 @@ func (p *Processor) ProcessMessage( slog.Info( "updating message status", "status", relayer.EventStatus(messageStatus).String(), - "occuredtxHash", event.Raw.TxHash.Hex(), + "occuredtxHash", msgBody.Event.Raw.TxHash.Hex(), "processedTxHash", hex.EncodeToString(tx.Hash().Bytes()), ) @@ -132,8 +198,8 @@ func (p *Processor) ProcessMessage( } // update message status - if err := p.eventRepo.UpdateStatus(ctx, e.ID, relayer.EventStatus(messageStatus)); err != nil { - return errors.Wrap(err, "s.eventRepo.UpdateStatus") + if err := p.eventRepo.UpdateStatus(ctx, msgBody.ID, relayer.EventStatus(messageStatus)); err != nil { + return errors.Wrap(err, fmt.Sprintf("p.eventRepo.UpdateStatus, id: %v", msgBody.ID)) } return nil @@ -243,19 +309,26 @@ func (p *Processor) needsContractDeployment( chainID := canonicalToken.ChainID() addr := canonicalToken.Address() + ctx, cancel := context.WithTimeout(ctx, p.ethClientTimeout) + defer cancel() + + opts := &bind.CallOpts{ + Context: ctx, + } + if eventType == relayer.EventTypeSendERC20 && event.Message.DestChainId.Cmp(chainID) != 0 { // determine whether the canonical token is bridged or not on this chain - bridgedAddress, err = p.destERC20Vault.CanonicalToBridged(nil, chainID, addr) + bridgedAddress, err = p.destERC20Vault.CanonicalToBridged(opts, chainID, addr) } if eventType == relayer.EventTypeSendERC721 && event.Message.DestChainId.Cmp(chainID) != 0 { // determine whether the canonical token is bridged or not on this chain - bridgedAddress, err = p.destERC721Vault.CanonicalToBridged(nil, chainID, addr) + bridgedAddress, err = p.destERC721Vault.CanonicalToBridged(opts, chainID, addr) } if eventType == relayer.EventTypeSendERC1155 && event.Message.DestChainId.Cmp(chainID) != 0 { // determine whether the canonical token is bridged or not on this chain - bridgedAddress, err = p.destERC1155Vault.CanonicalToBridged(nil, chainID, addr) + bridgedAddress, err = p.destERC1155Vault.CanonicalToBridged(opts, chainID, addr) } if err != nil { @@ -345,7 +418,6 @@ func (p *Processor) setLatestNonce(nonce uint64) { func (p *Processor) saveMessageStatusChangedEvent( ctx context.Context, receipt *types.Receipt, - e *relayer.Event, event *bridge.BridgeMessageSent, ) error { bridgeAbi, err := abi.JSON(strings.NewReader(bridge.BridgeABI)) @@ -376,8 +448,8 @@ func (p *Processor) saveMessageStatusChangedEvent( Data: data, ChainID: event.Message.DestChainId, Status: relayer.EventStatus(m["status"].(uint8)), - MsgHash: e.MsgHash, - MessageOwner: e.MessageOwner, + MsgHash: common.Hash(event.MsgHash).Hex(), + MessageOwner: event.Message.User.Hex(), Event: relayer.EventNameMessageStatusChanged, }) if err != nil { diff --git a/packages/relayer/processor/process_message_test.go b/packages/relayer/processor/process_message_test.go new file mode 100644 index 0000000000..b6dae2c8a5 --- /dev/null +++ b/packages/relayer/processor/process_message_test.go @@ -0,0 +1,187 @@ +package processor + +import ( + "context" + "encoding/json" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/taikoxyz/taiko-mono/packages/relayer" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/mock" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" +) + +func Test_sendProcessMessageCall(t *testing.T) { + p := newTestProcessor(true) + + _, err := p.sendProcessMessageCall( + context.Background(), + &bridge.BridgeMessageSent{ + Message: bridge.IBridgeMessage{ + DestChainId: mock.MockChainID, + Fee: new(big.Int).Add(mock.ProcessMessageTx.Cost(), big.NewInt(1)), + }, + Raw: types.Log{ + Address: relayer.ZeroAddress, + Topics: []common.Hash{ + relayer.ZeroHash, + }, + Data: []byte{0xff}, + }, + }, []byte{}) + + assert.Nil(t, err) + + assert.Equal(t, p.destNonce, mock.PendingNonce) +} + +func Test_ProcessMessage_messageUnprocessable(t *testing.T) { + p := newTestProcessor(true) + body := &queue.QueueMessageBody{ + Event: &bridge.BridgeMessageSent{ + Message: bridge.IBridgeMessage{ + GasLimit: big.NewInt(1), + }, + Raw: types.Log{ + Address: relayer.ZeroAddress, + Topics: []common.Hash{ + relayer.ZeroHash, + }, + Data: []byte{0xff}, + }, + }, + ID: 0, + } + + marshalled, err := json.Marshal(body) + assert.Nil(t, err) + + msg := queue.Message{ + Body: marshalled, + } + + err = p.processMessage(context.Background(), msg) + assert.EqualError(t, err, "message is unprocessable") +} + +func Test_ProcessMessage_gasLimit0(t *testing.T) { + p := newTestProcessor(true) + + body := queue.QueueMessageBody{ + Event: &bridge.BridgeMessageSent{ + Message: bridge.IBridgeMessage{ + GasLimit: big.NewInt(0), + }, + Raw: types.Log{ + Address: relayer.ZeroAddress, + Topics: []common.Hash{ + relayer.ZeroHash, + }, + Data: []byte{0xff}, + }, + }, + ID: 0, + } + + marshalled, err := json.Marshal(body) + assert.Nil(t, err) + + msg := queue.Message{ + Body: marshalled, + } + + err = p.processMessage(context.Background(), msg) + assert.EqualError(t, errors.New("only user can process this, gasLimit set to 0"), err.Error()) +} + +func Test_ProcessMessage_noChainId(t *testing.T) { + p := newTestProcessor(true) + + body := queue.QueueMessageBody{ + Event: &bridge.BridgeMessageSent{ + Message: bridge.IBridgeMessage{ + GasLimit: big.NewInt(1), + }, + MsgHash: mock.SuccessMsgHash, + Raw: types.Log{ + Address: relayer.ZeroAddress, + Topics: []common.Hash{ + relayer.ZeroHash, + }, + Data: []byte{0xff}, + }, + }, + ID: 0, + } + + marshalled, err := json.Marshal(body) + assert.Nil(t, err) + + msg := queue.Message{ + Body: marshalled, + } + + err = p.processMessage(context.Background(), msg) + assert.EqualError(t, err, "bind.NewKeyedTransactorWithChainID: no chain id specified") +} + +func Test_ProcessMessage(t *testing.T) { + p := newTestProcessor(true) + + body := queue.QueueMessageBody{ + Event: &bridge.BridgeMessageSent{ + Message: bridge.IBridgeMessage{ + GasLimit: big.NewInt(1), + DestChainId: mock.MockChainID, + Fee: big.NewInt(1000000000), + SrcChainId: mock.MockChainID, + }, + MsgHash: mock.SuccessMsgHash, + Raw: types.Log{ + Address: relayer.ZeroAddress, + Topics: []common.Hash{ + relayer.ZeroHash, + }, + Data: []byte{0xff}, + }, + }, + ID: 0, + } + + marshalled, err := json.Marshal(body) + assert.Nil(t, err) + + msg := queue.Message{ + Body: marshalled, + } + + err = p.processMessage(context.Background(), msg) + + assert.Nil( + t, + err, + ) +} + +// func Test_ProcessMessage_unprofitable(t *testing.T) { +// p := newTestProcessor(true) + +// err := p.ProcessMessage(context.Background(), &bridge.BridgeMessageSent{ +// Message: bridge.IBridgeMessage{ +// GasLimit: big.NewInt(1), +// DestChainId: mock.MockChainID, +// }, +// Signal: mock.SuccessMsgHash, +// }, &relayer.Event{}) + +// assert.EqualError( +// t, +// err, +// "p.sendProcessMessageCall: "+relayer.ErrUnprofitable.Error(), +// ) +// } diff --git a/packages/relayer/processor/processor.go b/packages/relayer/processor/processor.go new file mode 100644 index 0000000000..f9e2c2d264 --- /dev/null +++ b/packages/relayer/processor/processor.go @@ -0,0 +1,313 @@ +package processor + +import ( + "context" + "crypto/ecdsa" + "database/sql" + "errors" + "fmt" + "log/slog" + "math/big" + "sync" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" + "gorm.io/gorm" + + "github.com/taikoxyz/taiko-mono/packages/relayer" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/erc1155vault" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/erc20vault" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/erc721vault" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/icrosschainsync" + "github.com/taikoxyz/taiko-mono/packages/relayer/proof" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" + "github.com/taikoxyz/taiko-mono/packages/relayer/repo" +) + +type DB interface { + DB() (*sql.DB, error) + GormDB() *gorm.DB +} + +type ethClient interface { + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + BlockNumber(ctx context.Context) (uint64, error) + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + SuggestGasPrice(ctx context.Context) (*big.Int, error) + SuggestGasTipCap(ctx context.Context) (*big.Int, error) + ChainID(ctx context.Context) (*big.Int, error) +} + +type Processor struct { + cancel context.CancelFunc + + eventRepo relayer.EventRepository + + queue queue.Queue + + srcEthClient ethClient + destEthClient ethClient + rpc relayer.Caller + + ecdsaKey *ecdsa.PrivateKey + + destBridge relayer.Bridge + destHeaderSyncer relayer.HeaderSyncer + destERC20Vault relayer.TokenVault + destERC1155Vault relayer.TokenVault + destERC721Vault relayer.TokenVault + + prover *proof.Prover + + mu *sync.Mutex + + destNonce uint64 + relayerAddr common.Address + srcSignalServiceAddress common.Address + confirmations uint64 + + profitableOnly bool + headerSyncIntervalSeconds int64 + + confTimeoutInSeconds int64 + + backOffRetryInterval time.Duration + backOffMaxRetries uint64 + ethClientTimeout time.Duration + + msgCh chan queue.Message + + wg *sync.WaitGroup + + srcChainId *big.Int + destChainId *big.Int +} + +func (p *Processor) InitFromCli(ctx context.Context, c *cli.Context) error { + cfg, err := NewConfigFromCliContext(c) + if err != nil { + return err + } + + return InitFromConfig(ctx, p, cfg) +} + +// nolint: funlen +func InitFromConfig(ctx context.Context, p *Processor, cfg *Config) error { + db, err := cfg.OpenDBFunc() + if err != nil { + return err + } + + eventRepository, err := repo.NewEventRepository(db) + if err != nil { + return err + } + + srcRpcClient, err := rpc.Dial(cfg.SrcRPCUrl) + if err != nil { + return err + } + + srcEthClient, err := ethclient.Dial(cfg.SrcRPCUrl) + if err != nil { + return err + } + + destEthClient, err := ethclient.Dial(cfg.DestRPCUrl) + if err != nil { + return err + } + + q, err := cfg.OpenQueueFunc() + if err != nil { + return err + } + + destHeaderSyncer, err := icrosschainsync.NewICrossChainSync( + cfg.DestTaikoAddress, + destEthClient, + ) + if err != nil { + return err + } + + destERC20Vault, err := erc20vault.NewERC20Vault( + cfg.DestERC20VaultAddress, + destEthClient, + ) + if err != nil { + return err + } + + var destERC721Vault *erc721vault.ERC721Vault + if cfg.DestERC721VaultAddress.Hex() != relayer.ZeroAddress.Hex() { + destERC721Vault, err = erc721vault.NewERC721Vault(cfg.DestERC721VaultAddress, destEthClient) + if err != nil { + return err + } + } + + var destERC1155Vault *erc1155vault.ERC1155Vault + if cfg.DestERC1155VaultAddress.Hex() != relayer.ZeroAddress.Hex() { + destERC1155Vault, err = erc1155vault.NewERC1155Vault( + cfg.DestERC1155VaultAddress, + destEthClient, + ) + if err != nil { + return err + } + } + + destBridge, err := bridge.NewBridge(cfg.DestBridgeAddress, destEthClient) + if err != nil { + return err + } + + srcChainID, err := srcEthClient.ChainID(context.Background()) + if err != nil { + return err + } + + destChainID, err := destEthClient.ChainID(context.Background()) + if err != nil { + return err + } + + prover, err := proof.New(srcEthClient) + if err != nil { + return err + } + + publicKey := cfg.ProcessorPrivateKey.Public() + + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return errors.New("unable to convert public key") + } + + relayerAddr := crypto.PubkeyToAddress(*publicKeyECDSA) + + p.prover = prover + p.eventRepo = eventRepository + + p.srcEthClient = srcEthClient + p.destEthClient = destEthClient + + p.destBridge = destBridge + p.destERC1155Vault = destERC1155Vault + p.destERC20Vault = destERC20Vault + p.destERC721Vault = destERC721Vault + p.destHeaderSyncer = destHeaderSyncer + + p.ecdsaKey = cfg.ProcessorPrivateKey + p.relayerAddr = relayerAddr + + p.profitableOnly = cfg.ProfitableOnly + + p.queue = q + + p.srcChainId = srcChainID + p.destChainId = destChainID + + p.headerSyncIntervalSeconds = int64(cfg.HeaderSyncInterval) + p.confTimeoutInSeconds = int64(cfg.ConfirmationsTimeout) + p.confirmations = cfg.Confirmations + + p.srcSignalServiceAddress = cfg.SrcSignalServiceAddress + + p.msgCh = make(chan queue.Message) + p.wg = &sync.WaitGroup{} + p.mu = &sync.Mutex{} + p.rpc = srcRpcClient + + p.backOffRetryInterval = time.Duration(cfg.BackoffRetryInterval) * time.Second + p.backOffMaxRetries = cfg.BackOffMaxRetrys + p.ethClientTimeout = time.Duration(cfg.ETHClientTimeout) * time.Second + + return nil +} + +func (p *Processor) Name() string { + return "processor" +} + +func (p *Processor) Close(ctx context.Context) { + p.cancel() + + p.wg.Wait() +} + +func (p *Processor) Start() error { + ctx, cancel := context.WithCancel(context.Background()) + + p.cancel = cancel + + if err := p.queue.Start(ctx, p.queueName()); err != nil { + return err + } + + go func() { + if err := backoff.Retry(func() error { + slog.Info("attempting backoff queue subscription") + if err := p.queue.Subscribe(ctx, p.msgCh, p.wg); err != nil { + slog.Error("processor queue subscription error", "err", err.Error()) + return err + } + + return nil + }, backoff.NewConstantBackOff(1*time.Second)); err != nil { + slog.Error("rabbitmq subscribe backoff retry error", "err", err.Error()) + } + }() + + p.wg.Add(1) + go p.eventLoop(ctx) + + return nil +} + +func (p *Processor) queueName() string { + return fmt.Sprintf("%v-%v-queue", p.srcChainId.String(), p.destChainId.String()) +} + +func (p *Processor) eventLoop(ctx context.Context) { + defer func() { + p.wg.Done() + }() + + for { + select { + case <-ctx.Done(): + return + case msg := <-p.msgCh: + err := p.processMessage(ctx, msg) + + if err != nil { + slog.Error("err processing message", "err", err.Error()) + + if errors.Is(err, errUnprocessable) { + if err := p.queue.Ack(ctx, msg); err != nil { + slog.Error("Err acking message", "err", err.Error()) + } + } else { + if err := p.queue.Nack(ctx, msg); err != nil { + slog.Error("Err nacking message", "err", err.Error()) + } + } + } else { + if err := p.queue.Ack(ctx, msg); err != nil { + slog.Error("Err acking message", "err", err.Error()) + } + } + } + } +} diff --git a/packages/relayer/processor/processor_test.go b/packages/relayer/processor/processor_test.go new file mode 100644 index 0000000000..8c11790646 --- /dev/null +++ b/packages/relayer/processor/processor_test.go @@ -0,0 +1,41 @@ +package processor + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/taikoxyz/taiko-mono/packages/relayer/mock" + "github.com/taikoxyz/taiko-mono/packages/relayer/proof" +) + +var dummyEcdsaKey = "8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f" + +func newTestProcessor(profitableOnly bool) *Processor { + privateKey, _ := crypto.HexToECDSA(dummyEcdsaKey) + + prover, _ := proof.New( + &mock.Blocker{}, + ) + + return &Processor{ + eventRepo: &mock.EventRepository{}, + destBridge: &mock.Bridge{}, + srcEthClient: &mock.EthClient{}, + destEthClient: &mock.EthClient{}, + destERC20Vault: &mock.TokenVault{}, + mu: &sync.Mutex{}, + ecdsaKey: privateKey, + destHeaderSyncer: &mock.HeaderSyncer{}, + prover: prover, + rpc: &mock.Caller{}, + profitableOnly: profitableOnly, + headerSyncIntervalSeconds: 1, + confTimeoutInSeconds: 900, + confirmations: 1, + queue: &mock.Queue{}, + backOffRetryInterval: 1 * time.Second, + backOffMaxRetries: 1, + ethClientTimeout: 10 * time.Second, + } +} diff --git a/packages/relayer/message/wait_for_confirmations.go b/packages/relayer/processor/wait_for_confirmations.go similarity index 96% rename from packages/relayer/message/wait_for_confirmations.go rename to packages/relayer/processor/wait_for_confirmations.go index d67adc9391..beca79400c 100644 --- a/packages/relayer/message/wait_for_confirmations.go +++ b/packages/relayer/processor/wait_for_confirmations.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" diff --git a/packages/relayer/message/wait_for_confirmations_test.go b/packages/relayer/processor/wait_for_confirmations_test.go similarity index 94% rename from packages/relayer/message/wait_for_confirmations_test.go rename to packages/relayer/processor/wait_for_confirmations_test.go index 6b8f9a0e1b..e47135f0a1 100644 --- a/packages/relayer/message/wait_for_confirmations_test.go +++ b/packages/relayer/processor/wait_for_confirmations_test.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" diff --git a/packages/relayer/message/wait_header_synced.go b/packages/relayer/processor/wait_header_synced.go similarity index 95% rename from packages/relayer/message/wait_header_synced.go rename to packages/relayer/processor/wait_header_synced.go index 96497bda39..2c8465546e 100644 --- a/packages/relayer/message/wait_header_synced.go +++ b/packages/relayer/processor/wait_header_synced.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) func (p *Processor) waitHeaderSynced(ctx context.Context, event *bridge.BridgeMessageSent) error { diff --git a/packages/relayer/message/wait_header_synced_test.go b/packages/relayer/processor/wait_header_synced_test.go similarity index 79% rename from packages/relayer/message/wait_header_synced_test.go rename to packages/relayer/processor/wait_header_synced_test.go index 46c60b7892..6ab4f2072d 100644 --- a/packages/relayer/message/wait_header_synced_test.go +++ b/packages/relayer/processor/wait_header_synced_test.go @@ -1,4 +1,4 @@ -package message +package processor import ( "context" @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" - "github.com/taikoxyz/taiko-mono/packages/relayer/contracts/bridge" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" ) func Test_waitHeaderSynced(t *testing.T) { diff --git a/packages/relayer/proof/encoded_signal_proof.go b/packages/relayer/proof/encoded_signal_proof.go index 1d34d6980f..8a1730d064 100644 --- a/packages/relayer/proof/encoded_signal_proof.go +++ b/packages/relayer/proof/encoded_signal_proof.go @@ -4,7 +4,8 @@ import ( "context" "math/big" - "github.com/labstack/gommon/log" + "log/slog" + "github.com/taikoxyz/taiko-mono/packages/relayer" "github.com/taikoxyz/taiko-mono/packages/relayer/encoding" @@ -58,7 +59,11 @@ func (p *Prover) encodedStorageProof( ) ([]byte, error) { var ethProof StorageProof - log.Infof("getting proof for: %v, key: %v, blockNum: %v", signalServiceAddress, key, blockNumber) + slog.Info("getting proof", + "signalServiceAddress", signalServiceAddress.Hex(), + "key", key, + "blockNum", blockNumber, + ) err := c.CallContext(ctx, ðProof, @@ -71,7 +76,7 @@ func (p *Prover) encodedStorageProof( return nil, errors.Wrap(err, "c.CallContext") } - log.Infof("proof: %v", new(big.Int).SetBytes(ethProof.StorageProof[0].Value).Int64()) + slog.Info("proof generated", "value", new(big.Int).SetBytes(ethProof.StorageProof[0].Value).Int64()) if new(big.Int).SetBytes(ethProof.StorageProof[0].Value).Int64() != int64(1) { return nil, errors.New("proof will not be valid, expected storageProof to be 1 but was not") diff --git a/packages/relayer/queue/queue.go b/packages/relayer/queue/queue.go new file mode 100644 index 0000000000..6dba516f6e --- /dev/null +++ b/packages/relayer/queue/queue.go @@ -0,0 +1,40 @@ +package queue + +import ( + "context" + "errors" + "sync" + + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge" +) + +var ( + ErrClosed = errors.New("queue connection closed") +) + +type Queue interface { + Start(ctx context.Context, queueName string) error + Close(ctx context.Context) + Publish(ctx context.Context, msg []byte) error + Notify(ctx context.Context, wg *sync.WaitGroup) error + Subscribe(ctx context.Context, msgs chan<- Message, wg *sync.WaitGroup) error + Ack(ctx context.Context, msg Message) error + Nack(ctx context.Context, msg Message) error +} + +type QueueMessageBody struct { + Event *bridge.BridgeMessageSent + ID int +} + +type Message struct { + Body []byte + Internal interface{} +} + +type NewQueueOpts struct { + Username string + Password string + Host string + Port string +} diff --git a/packages/relayer/queue/rabbitmq/queue.go b/packages/relayer/queue/rabbitmq/queue.go new file mode 100644 index 0000000000..36a01ce92d --- /dev/null +++ b/packages/relayer/queue/rabbitmq/queue.go @@ -0,0 +1,310 @@ +package rabbitmq + +import ( + "context" + "fmt" + "log/slog" + "sync" + "time" + + "github.com/google/uuid" + amqp "github.com/rabbitmq/amqp091-go" + "github.com/taikoxyz/taiko-mono/packages/relayer/queue" +) + +type RabbitMQ struct { + conn *amqp.Connection + ch *amqp.Channel + queue amqp.Queue + opts queue.NewQueueOpts + + connErrCh chan *amqp.Error + + chErrCh chan *amqp.Error + + notifyReturnCh chan amqp.Return + + subscriptionCtx context.Context + subscriptionCancel context.CancelFunc +} + +func NewQueue(opts queue.NewQueueOpts) (*RabbitMQ, error) { + slog.Info("dialing rabbitmq connection") + + r := &RabbitMQ{ + opts: opts, + } + + err := r.connect() + if err != nil { + return nil, err + } + + return r, nil +} + +func (r *RabbitMQ) connect() error { + slog.Info("connecting to rabbitmq") + + if r.subscriptionCancel != nil { + r.subscriptionCancel() + } + + conn, err := amqp.DialConfig( + fmt.Sprintf( + "amqp://%v:%v@%v:%v/", + r.opts.Username, + r.opts.Password, + r.opts.Host, + r.opts.Port, + ), amqp.Config{ + Heartbeat: 1 * time.Second, + }) + if err != nil { + return err + } + + ch, err := conn.Channel() + if err != nil { + return err + } + + r.conn = conn + r.ch = ch + + r.connErrCh = r.conn.NotifyClose(make(chan *amqp.Error, 1)) + + r.chErrCh = r.ch.NotifyClose(make(chan *amqp.Error, 1)) + + r.subscriptionCtx, r.subscriptionCancel = context.WithCancel(context.Background()) + + slog.Info("connected to rabbitmq") + + return nil +} + +func (r *RabbitMQ) Start(ctx context.Context, queueName string) error { + slog.Info("declaring rabbitmq queue", "queue", queueName) + + q, err := r.ch.QueueDeclare( + queueName, + true, + false, + false, + false, + nil, + ) + if err != nil { + return err + } + + r.queue = q + + return nil +} + +func (r *RabbitMQ) Close(ctx context.Context) { + if err := r.ch.Close(); err != nil { + if err != amqp.ErrClosed { + slog.Info("error closing rabbitmq connection", "err", err.Error()) + } + } + + slog.Info("closed rabbitmq channel") + + if err := r.conn.Close(); err != nil { + if err != amqp.ErrClosed { + slog.Info("error closing rabbitmq connection", "err", err.Error()) + } + } + + slog.Info("closed rabbitmq connection") +} + +func (r *RabbitMQ) Publish(ctx context.Context, msg []byte) error { + slog.Info("publishing rabbitmq msg to queue", "queue", r.queue.Name) + + err := r.ch.PublishWithContext(ctx, + "", + r.queue.Name, + true, + false, + amqp.Publishing{ + ContentType: "text/plain", + Body: msg, + MessageId: uuid.New().String(), + }) + if err != nil { + if err == amqp.ErrClosed { + slog.Error("amqp channel closed", "err", err.Error()) + + err := r.connect() + if err != nil { + return err + } + + return r.Publish(ctx, msg) + } else { + return err + } + } + + return nil +} + +func (r *RabbitMQ) Ack(ctx context.Context, msg queue.Message) error { + rmqMsg := msg.Internal.(amqp.Delivery) + + slog.Info("acknowledging rabbitmq message", "msgId", rmqMsg.MessageId) + + err := rmqMsg.Ack(false) + + slog.Info("attempted acknowledge rabbitmq message") + + if err != nil { + slog.Error("error acknowledging rabbitmq message", "err", err.Error()) + return err + } + + slog.Info("acknowledged rabbitmq message", "msgId", rmqMsg.MessageId) + + return nil +} + +func (r *RabbitMQ) Nack(ctx context.Context, msg queue.Message) error { + rmqMsg := msg.Internal.(amqp.Delivery) + + slog.Info("negatively acknowledging rabbitmq message", "msgId", rmqMsg.MessageId) + + err := rmqMsg.Nack(false, false) + if err != nil { + slog.Error("error negatively acknowledging rabbitmq message", "err", err.Error()) + return err + } + + slog.Info("negatively acknowledged rabbitmq message", "msgId", rmqMsg.MessageId) + + return nil +} + +// Notify should be called by publishers who wish to be notified of subscription errors. +func (r *RabbitMQ) Notify(ctx context.Context, wg *sync.WaitGroup) error { + wg.Add(1) + + defer func() { + wg.Done() + }() + + slog.Info("rabbitmq notify running") + + for { + select { + case <-ctx.Done(): + slog.Info("rabbitmq context closed") + + return nil + case err := <-r.connErrCh: + slog.Error("rabbitmq notify close connection", "err", err.Error()) + return queue.ErrClosed + case err := <-r.chErrCh: + slog.Error("rabbitmq notify close channel", "err", err.Error()) + return queue.ErrClosed + case returnMsg := <-r.notifyReturnCh: + slog.Error("rabbitmq notify return", "id", returnMsg.MessageId, "err", returnMsg.ReplyText) + slog.Info("rabbitmq attempting republish of returned msg", "id", returnMsg.MessageId) + + if err := r.Publish(ctx, returnMsg.Body); err != nil { + slog.Error("error publishing msg", "err", err.Error()) + } + } + } +} + +// Subscribe should be called by consumers. +func (r *RabbitMQ) Subscribe(ctx context.Context, msgChan chan<- queue.Message, wg *sync.WaitGroup) error { + wg.Add(1) + + defer func() { + wg.Done() + }() + + slog.Info("subscribing to rabbitmq messages", "queue", r.queue.Name) + + msgs, err := r.ch.Consume( + r.queue.Name, + "", + false, // disable auto-acknowledge until after processing + false, + false, + false, + nil, + ) + + if err != nil { + if err == amqp.ErrClosed { + slog.Info("cant subscribe to rabbitmq, channel closed. attempting reconnection") + + if err := r.connect(); err != nil { + slog.Error("error reconnecting to channel during subscribe", "err", err.Error()) + return err + } + + msgs, err = r.ch.Consume( + r.queue.Name, + "", + false, // disable auto-acknowledge until after processing + false, + false, + false, + nil, + ) + if err != nil { + return err + } + } else { + return err + } + } + + for { + select { + case <-r.subscriptionCtx.Done(): + defer r.Close(ctx) + + slog.Info("rabbitmq subscription ctx cancelled") + + return queue.ErrClosed + case <-ctx.Done(): + defer r.Close(ctx) + + slog.Info("rabbitmq context cancelled") + + return nil + case err := <-r.connErrCh: + slog.Error("rabbitmq notify close connection", "err", err.Error()) + + return queue.ErrClosed + case err := <-r.chErrCh: + slog.Error("rabbitmq notify close channel", "err", err.Error()) + + return queue.ErrClosed + case d, ok := <-msgs: + if !ok { + slog.Error("rabbitmq msg channel was closed") + return queue.ErrClosed + } + + if d.Body != nil { + slog.Info("rabbitmq message found", "msgId", d.MessageId) + + msgChan <- queue.Message{ + Body: d.Body, + Internal: d, + } + } else { + slog.Info("nil body message, queue is closed") + return queue.ErrClosed + } + } + } +} diff --git a/packages/relayer/repo/block.go b/packages/relayer/repo/block.go index 4ee757b264..b0d349b85d 100644 --- a/packages/relayer/repo/block.go +++ b/packages/relayer/repo/block.go @@ -8,12 +8,12 @@ import ( ) type BlockRepository struct { - db relayer.DB + db DB } -func NewBlockRepository(db relayer.DB) (*BlockRepository, error) { +func NewBlockRepository(db DB) (*BlockRepository, error) { if db == nil { - return nil, relayer.ErrNoDB + return nil, ErrNoDB } return &BlockRepository{ diff --git a/packages/relayer/repo/block_test.go b/packages/relayer/repo/block_test.go index 52a124ea67..4aabd6d29b 100644 --- a/packages/relayer/repo/block_test.go +++ b/packages/relayer/repo/block_test.go @@ -13,7 +13,7 @@ import ( func Test_NewBlockRepo(t *testing.T) { tests := []struct { name string - db relayer.DB + db DB wantErr error }{ { @@ -24,7 +24,7 @@ func Test_NewBlockRepo(t *testing.T) { { "noDb", nil, - relayer.ErrNoDB, + ErrNoDB, }, } diff --git a/packages/relayer/repo/containers_test.go b/packages/relayer/repo/containers_test.go index ac5057c970..2d2638fc01 100644 --- a/packages/relayer/repo/containers_test.go +++ b/packages/relayer/repo/containers_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/pressly/goose/v3" - "github.com/taikoxyz/taiko-mono/packages/relayer" "github.com/taikoxyz/taiko-mono/packages/relayer/db" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -21,7 +20,7 @@ var ( dbPassword = "password" ) -func testMysql(t *testing.T) (relayer.DB, func(), error) { +func testMysql(t *testing.T) (DB, func(), error) { req := testcontainers.ContainerRequest{ Image: "mysql:latest", ExposedPorts: []string{"3306/tcp", "33060/tcp"}, diff --git a/packages/relayer/repo/db.go b/packages/relayer/repo/db.go new file mode 100644 index 0000000000..1c8a35dbca --- /dev/null +++ b/packages/relayer/repo/db.go @@ -0,0 +1,17 @@ +package repo + +import ( + "database/sql" + + "github.com/cyberhorsey/errors" + "gorm.io/gorm" +) + +var ( + ErrNoDB = errors.Validation.NewWithKeyAndDetail("ERR_NO_DB", "no db") +) + +type DB interface { + DB() (*sql.DB, error) + GormDB() *gorm.DB +} diff --git a/packages/relayer/repo/event.go b/packages/relayer/repo/event.go index 60ee33b27f..a8b40e362c 100644 --- a/packages/relayer/repo/event.go +++ b/packages/relayer/repo/event.go @@ -14,12 +14,12 @@ import ( ) type EventRepository struct { - db relayer.DB + db DB } -func NewEventRepository(db relayer.DB) (*EventRepository, error) { +func NewEventRepository(db DB) (*EventRepository, error) { if db == nil { - return nil, relayer.ErrNoDB + return nil, ErrNoDB } return &EventRepository{ diff --git a/packages/relayer/repo/event_test.go b/packages/relayer/repo/event_test.go index 59af84ec47..976058806d 100644 --- a/packages/relayer/repo/event_test.go +++ b/packages/relayer/repo/event_test.go @@ -79,7 +79,7 @@ var testEvents = []relayer.Event{ func Test_NewEventRepo(t *testing.T) { tests := []struct { name string - db relayer.DB + db DB wantErr error }{ { @@ -90,7 +90,7 @@ func Test_NewEventRepo(t *testing.T) { { "noDb", nil, - relayer.ErrNoDB, + ErrNoDB, }, } diff --git a/packages/relayer/abigen.sh b/packages/relayer/scripts/abigen.sh similarity index 94% rename from packages/relayer/abigen.sh rename to packages/relayer/scripts/abigen.sh index e20e84244a..6344eb4ca6 100755 --- a/packages/relayer/abigen.sh +++ b/packages/relayer/scripts/abigen.sh @@ -16,7 +16,7 @@ do abigen --abi ${names[i]}.json \ --pkg $lower \ --type ${names[i]} \ - --out contracts/$lower/${names[i]}.go + --out bindings/$lower/${names[i]}.go done exit 0 diff --git a/packages/relayer/scripts/install-rabbit-mq.sh b/packages/relayer/scripts/install-rabbit-mq.sh new file mode 100755 index 0000000000..52d438c7c3 --- /dev/null +++ b/packages/relayer/scripts/install-rabbit-mq.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +sudo apt-get install curl gnupg apt-transport-https -y + +## Team RabbitMQ's main signing key +curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.rabbitmq.team.gpg > /dev/null +## Community mirror of Cloudsmith: modern Erlang repository +curl -1sLf https://ppa.novemberain.com/gpg.E495BB49CC4BBE5B.key | sudo gpg --dearmor | sudo tee /usr/share/keyrings/rabbitmq.E495BB49CC4BBE5B.gpg > /dev/null +## Community mirror of Cloudsmith: RabbitMQ repository +curl -1sLf https://ppa.novemberain.com/gpg.9F4587F226208342.key | sudo gpg --dearmor | sudo tee /usr/share/keyrings/rabbitmq.9F4587F226208342.gpg > /dev/null + +## Add apt repositories maintained by Team RabbitMQ +sudo tee /etc/apt/sources.list.d/rabbitmq.list <