From 9c526dbaffdc2a11d69d8495ac00f78a8a2e6b7c Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 23 Mar 2023 13:53:38 -0700 Subject: [PATCH 01/20] eventindexer for blockproven / potentially other events, API for status page --- .github/workflows/eventindexer.yml | 97 + packages/eventindexer/.default.env | 13 + packages/eventindexer/.gitignore | 46 + packages/eventindexer/.golangci.yml | 45 + packages/eventindexer/CHANGELOG.md | 63 + packages/eventindexer/Dockerfile | 19 + packages/eventindexer/README.md | 12 + packages/eventindexer/TaikoL1.json | 1134 +++++++++ packages/eventindexer/abigen.sh | 22 + packages/eventindexer/block.go | 31 + packages/eventindexer/caller.go | 7 + packages/eventindexer/cli/cli.go | 253 ++ packages/eventindexer/cmd/main.go | 31 + .../eventindexer/contracts/taikol1/TaikoL1.go | 2031 +++++++++++++++++ packages/eventindexer/db.go | 25 + packages/eventindexer/db/db.go | 25 + packages/eventindexer/errors.go | 15 + packages/eventindexer/event.go | 46 + packages/eventindexer/flags.go | 20 + packages/eventindexer/http/errors.go | 14 + .../eventindexer/http/get_unique_provers.go | 28 + packages/eventindexer/http/routes.go | 8 + packages/eventindexer/http/server.go | 126 + .../indexer/filter_then_subscribe.go | 112 + .../indexer/save_block_proven_event.go | 60 + packages/eventindexer/indexer/service.go | 78 + .../set_initial_processing_block_height.go | 45 + packages/eventindexer/indexer/subscribe.go | 86 + .../1666650599_create_events_table.sql | 18 + ...66650700_create_processed_blocks_table.sql | 17 + packages/eventindexer/package.json | 5 + packages/eventindexer/prometheus.go | 29 + packages/eventindexer/repo/block.go | 62 + packages/eventindexer/repo/event.go | 53 + 34 files changed, 4676 insertions(+) create mode 100644 .github/workflows/eventindexer.yml create mode 100644 packages/eventindexer/.default.env create mode 100644 packages/eventindexer/.gitignore create mode 100644 packages/eventindexer/.golangci.yml create mode 100644 packages/eventindexer/CHANGELOG.md create mode 100644 packages/eventindexer/Dockerfile create mode 100644 packages/eventindexer/README.md create mode 100644 packages/eventindexer/TaikoL1.json create mode 100755 packages/eventindexer/abigen.sh create mode 100644 packages/eventindexer/block.go create mode 100644 packages/eventindexer/caller.go create mode 100644 packages/eventindexer/cli/cli.go create mode 100644 packages/eventindexer/cmd/main.go create mode 100644 packages/eventindexer/contracts/taikol1/TaikoL1.go create mode 100644 packages/eventindexer/db.go create mode 100644 packages/eventindexer/db/db.go create mode 100644 packages/eventindexer/errors.go create mode 100644 packages/eventindexer/event.go create mode 100644 packages/eventindexer/flags.go create mode 100644 packages/eventindexer/http/errors.go create mode 100644 packages/eventindexer/http/get_unique_provers.go create mode 100644 packages/eventindexer/http/routes.go create mode 100644 packages/eventindexer/http/server.go create mode 100644 packages/eventindexer/indexer/filter_then_subscribe.go create mode 100644 packages/eventindexer/indexer/save_block_proven_event.go create mode 100644 packages/eventindexer/indexer/service.go create mode 100644 packages/eventindexer/indexer/set_initial_processing_block_height.go create mode 100644 packages/eventindexer/indexer/subscribe.go create mode 100644 packages/eventindexer/migrations/1666650599_create_events_table.sql create mode 100644 packages/eventindexer/migrations/1666650700_create_processed_blocks_table.sql create mode 100644 packages/eventindexer/package.json create mode 100644 packages/eventindexer/prometheus.go create mode 100644 packages/eventindexer/repo/block.go create mode 100644 packages/eventindexer/repo/event.go diff --git a/.github/workflows/eventindexer.yml b/.github/workflows/eventindexer.yml new file mode 100644 index 0000000000..4b326ee511 --- /dev/null +++ b/.github/workflows/eventindexer.yml @@ -0,0 +1,97 @@ +name: Eventindexer + +on: + push: + branches: [main, alpha-2] + paths: + - "packages/eventindexer/**" + pull_request: + paths: + - "packages/eventindexer/**" + +jobs: + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.19 + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: latest + + # Optional: working directory, useful for monorepos + working-directory: ./packages/eventindexer + args: --config=.golangci.yml + + test: + runs-on: ubuntu-latest + needs: lint + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ">=1.19.0" + + - name: eventindexer - Unit Tests + working-directory: ./packages/eventindexer + run: go test `go list ./... | grep -v ./contracts | grep -v ./mock | grep -v ./cmd` -coverprofile=coverage.txt -covermode=atomic + + - name: eventindexer - Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./packages/eventindexer/coverage.txt + flags: eventindexer + + push-docker-image: + # only push docker image on PR merge to main + if: ${{ github.event }} == 'push' + name: Build and push docker image + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Login to GCR + uses: docker/login-action@v2 + with: + registry: gcr.io + username: _json_key + password: ${{ secrets.GCR_JSON_KEY }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + gcr.io/evmchain/eventindexer + tags: | + type=ref,event=branch + type=ref,event=pr + type=ref,event=tag + type=sha + + - name: Build and push + uses: docker/build-push-action@v2 + with: + platforms: linux/amd64 + push: true + context: packages/eventindexer + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/packages/eventindexer/.default.env b/packages/eventindexer/.default.env new file mode 100644 index 0000000000..9b99382df7 --- /dev/null +++ b/packages/eventindexer/.default.env @@ -0,0 +1,13 @@ +HTTP_PORT=4101 +PROMETHEUS_HTTP_PORT=6061 +MYSQL_USER=root +MYSQL_PASSWORD=root +MYSQL_DATABASE=relayer +MYSQL_HOST=localhost:3306 +MYSQL_MAX_IDLE_CONNS=50 +MYSQL_MAX_OPEN_CONNS=3000 +MYSQL_CONN_MAX_LIFETIME_IN_MS=100000 +L1_TAIKO_ADDRESS=0x7B3AF414448ba906f02a1CA307C56c4ADFF27ce7 +L1_RPC_URL=wss://l1ws.a2.taiko.xyz +CORS_ORIGINS=* +BLOCK_BATCH_SIZE=10 \ No newline at end of file diff --git a/packages/eventindexer/.gitignore b/packages/eventindexer/.gitignore new file mode 100644 index 0000000000..eeedab188e --- /dev/null +++ b/packages/eventindexer/.gitignore @@ -0,0 +1,46 @@ +.netrc +.env +.test.env +main +coverage.txt + +# Local .terraform directories +.terraform + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +# +*.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +.idea + +Bridge.json +TaikoL2.json +IHeaderSync.json \ No newline at end of file diff --git a/packages/eventindexer/.golangci.yml b/packages/eventindexer/.golangci.yml new file mode 100644 index 0000000000..d29ee22f92 --- /dev/null +++ b/packages/eventindexer/.golangci.yml @@ -0,0 +1,45 @@ +# See: https://golangci-lint.run/usage/configuration/ +# +# Note: for VSCode, you must have the following settings to use this configuration: +# +# "go.lintTool": "golangci-lint", +# "go.lintFlags": [ +# "--fast", +# "--config=${workspaceFolder}/.golangci.yml" +# ], + +output: + format: colored-line-number + +linters: + enable: + - errcheck + - funlen + - gocognit + - gocritic + - gofmt + - golint + - gosec + - gosimple + - lll + - unused + - whitespace + - wsl + +linters-settings: + funlen: + lines: 137 + statements: 54 + gocognit: + min-complexity: 43 + +issues: + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - funlen + +run: + skip-dirs: + - contracts/* diff --git a/packages/eventindexer/CHANGELOG.md b/packages/eventindexer/CHANGELOG.md new file mode 100644 index 0000000000..81b325e3f1 --- /dev/null +++ b/packages/eventindexer/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +## [0.3.0](https://github.com/taikoxyz/taiko-mono/compare/relayer-v0.2.1...relayer-v0.3.0) (2023-03-15) + + +### Features + +* **relayer:** add msgHash and event type lookups to findallbyaddress ([#13310](https://github.com/taikoxyz/taiko-mono/issues/13310)) ([8b753ee](https://github.com/taikoxyz/taiko-mono/commit/8b753ee07eeee51adf48e72343b62abcde3b2338)) +* **relayer:** Event filter ([#13318](https://github.com/taikoxyz/taiko-mono/issues/13318)) ([f20d419](https://github.com/taikoxyz/taiko-mono/commit/f20d4195ac9d700dfd4a51192232c3fe7c4c0b43)) +* **relayer:** MessageStatusChanged events ([#13272](https://github.com/taikoxyz/taiko-mono/issues/13272)) ([f5f4fc4](https://github.com/taikoxyz/taiko-mono/commit/f5f4fc4af16520a34e805e8f16c50e0de4902815)) +* **relayer:** Pagination ([#13311](https://github.com/taikoxyz/taiko-mono/issues/13311)) ([9350006](https://github.com/taikoxyz/taiko-mono/commit/9350006aefa8f6423c663ea3a0377f7334a5b749)) + + +### Bug Fixes + +* **relayer:** estimate gas for tx, set gas to 2.5mil if not estimatable. works now. ([#13271](https://github.com/taikoxyz/taiko-mono/issues/13271)) ([3913ca5](https://github.com/taikoxyz/taiko-mono/commit/3913ca52242913dfb9502488f0a5558724f9ef2b)) + +## [0.2.1](https://github.com/taikoxyz/taiko-mono/compare/relayer-v0.2.0...relayer-v0.2.1) (2023-03-01) + + +### Bug Fixes + +* **relayer:** estimate gas, now that gas estimation works again ([#13176](https://github.com/taikoxyz/taiko-mono/issues/13176)) ([b7ae677](https://github.com/taikoxyz/taiko-mono/commit/b7ae677ec2d84dce3e3ae50d369bf31dedc547c3)) +* **relayer:** Save block progress when caught up and subscribing to new events ([#13177](https://github.com/taikoxyz/taiko-mono/issues/13177)) ([5ef2c0f](https://github.com/taikoxyz/taiko-mono/commit/5ef2c0f5d78764189d168aa527cec62238f1d6c6)) + +## [0.2.0](https://github.com/taikoxyz/taiko-mono/compare/relayer-v0.1.0...relayer-v0.2.0) (2023-02-15) + + +### Features + +* **protocol:** change statevariables to return a struct ([#13113](https://github.com/taikoxyz/taiko-mono/issues/13113)) ([0bffeb0](https://github.com/taikoxyz/taiko-mono/commit/0bffeb0f3d17938bf2146772962719ae21ce22fa)) +* **relayer:** catch relayer & status page up to new testnet ([#13114](https://github.com/taikoxyz/taiko-mono/issues/13114)) ([543f242](https://github.com/taikoxyz/taiko-mono/commit/543f242bfbf18b155f3476c2d172e79d3041ffc9)) +* **relayer:** prepare bridge relayer API for frontend ([#13124](https://github.com/taikoxyz/taiko-mono/issues/13124)) ([ef1f691](https://github.com/taikoxyz/taiko-mono/commit/ef1f691ac9e6b3138b1ee80bc7bebcf53b749581)) + +## [0.1.0](https://github.com/taikoxyz/taiko-mono/compare/relayer-v0.0.1...relayer-v0.1.0) (2023-01-19) + + +### Features + +* **bridge-ui:** process message ([#387](https://github.com/taikoxyz/taiko-mono/issues/387)) ([d1781c0](https://github.com/taikoxyz/taiko-mono/commit/d1781c0107110e70c87e76d3fc1f6a9bc2aa1a46)) +* **bridge:** add faucet link to announcement ([#485](https://github.com/taikoxyz/taiko-mono/issues/485)) ([d1a4921](https://github.com/taikoxyz/taiko-mono/commit/d1a492183fd4ab8f195697864f54c35349dca93d)) +* **bridge:** bridge design ([#369](https://github.com/taikoxyz/taiko-mono/issues/369)) ([04702db](https://github.com/taikoxyz/taiko-mono/commit/04702db23e3fd705133408e077b8d1a040951202)) +* **bridge:** bridge transactions ([#411](https://github.com/taikoxyz/taiko-mono/issues/411)) ([19dd7ab](https://github.com/taikoxyz/taiko-mono/commit/19dd7abd4a2f5bc83e43d31938e43501472ff108)) +* **bridge:** implement the bridge relayer ([#191](https://github.com/taikoxyz/taiko-mono/issues/191)) ([9f49e4c](https://github.com/taikoxyz/taiko-mono/commit/9f49e4c87304853c9d94693434d23a6b8258eac6)) +* implement release-please workflow ([#12967](https://github.com/taikoxyz/taiko-mono/issues/12967)) ([b0c8b60](https://github.com/taikoxyz/taiko-mono/commit/b0c8b60da0af3160db758f83c1f6368a3a712593)) +* **protocol:** implement & simulate tokenomics ([#376](https://github.com/taikoxyz/taiko-mono/issues/376)) ([191eb11](https://github.com/taikoxyz/taiko-mono/commit/191eb110990d60b49883eb3f3d7841c33421d067)) +* **relayer:** Allow resync flag option to restart processing from block 0 ([#266](https://github.com/taikoxyz/taiko-mono/issues/266)) ([6b01cbe](https://github.com/taikoxyz/taiko-mono/commit/6b01cbe986d61795fc9a2ef256dbe85409251720)) +* **relayer:** Asynchronous message processing, error handling, nonce management, and indexer folder structuring ([#259](https://github.com/taikoxyz/taiko-mono/issues/259)) ([ed6d551](https://github.com/taikoxyz/taiko-mono/commit/ed6d551744965440153eaa7a8c42c887fa26938c)) +* **relayer:** header sync check before processing messages ([#441](https://github.com/taikoxyz/taiko-mono/issues/441)) ([e9fda8b](https://github.com/taikoxyz/taiko-mono/commit/e9fda8bb80ecfefcfd7d64062b50ebf5b5eec2ef)) +* **relayer:** HTTP api for exposing events table for bridge UI ([#271](https://github.com/taikoxyz/taiko-mono/issues/271)) ([7b5e6b8](https://github.com/taikoxyz/taiko-mono/commit/7b5e6b809c0e2f6a8615896d57e2b0d2db98c80b)) +* **relayer:** only process profitable transactions ([#408](https://github.com/taikoxyz/taiko-mono/issues/408)) ([b5d8180](https://github.com/taikoxyz/taiko-mono/commit/b5d81802e32b038b5bcdd26f233b0cd4b3eca3fa)) +* **relayer:** run in http only mode, so we can scale up if necessary for requests and only have one indexer ([6500234](https://github.com/taikoxyz/taiko-mono/commit/6500234991702b203e6e8baeb496e5473b631f83)) +* **relayer:** Wait N confirmations on source chain before processing message on destination chain ([#270](https://github.com/taikoxyz/taiko-mono/issues/270)) ([7ab1291](https://github.com/taikoxyz/taiko-mono/commit/7ab129193f3e08faf04cd1b7e09b5b5994636775)) + + +### Bug Fixes + +* **bridge-ui:** Eth fix ([#475](https://github.com/taikoxyz/taiko-mono/issues/475)) ([08175b8](https://github.com/taikoxyz/taiko-mono/commit/08175b803aaabdf6195f5a7a3ed8e0baf9558cc5)) +* **protocol:** Remove enableDestChain functionality ([#12341](https://github.com/taikoxyz/taiko-mono/issues/12341)) ([362d083](https://github.com/taikoxyz/taiko-mono/commit/362d083497cc74b3bcd05a406beeff2101a422ef)) +* **relayer:** fix migrations ([#300](https://github.com/taikoxyz/taiko-mono/issues/300)) ([151415e](https://github.com/taikoxyz/taiko-mono/commit/151415e71f2b6ac62c607d5cc928fa258064a679)) +* **relayer:** gas limit + use loading as priorioty on bridge form ([#487](https://github.com/taikoxyz/taiko-mono/issues/487)) ([3747d4c](https://github.com/taikoxyz/taiko-mono/commit/3747d4c41e836ab533e864ec44073ae681bf4b36)) +* **relayer:** save block by chain id ([#379](https://github.com/taikoxyz/taiko-mono/issues/379)) ([608e3e3](https://github.com/taikoxyz/taiko-mono/commit/608e3e3723586f8b412d71118d15f6bab86ad596)) +* **tests:** cleanup tests to prepare for tokenomics testing ([#11316](https://github.com/taikoxyz/taiko-mono/issues/11316)) ([d63fae3](https://github.com/taikoxyz/taiko-mono/commit/d63fae30f1e3415d6f377adeab90c062fed5ad42)) diff --git a/packages/eventindexer/Dockerfile b/packages/eventindexer/Dockerfile new file mode 100644 index 0000000000..c9528c3ff5 --- /dev/null +++ b/packages/eventindexer/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.19.3 as builder + +RUN apt install git curl + +RUN git clone --depth 1 https://github.com/taikoxyz/taiko-mono /taiko-mono + +WORKDIR /taiko-mono/packages/eventindexer + +RUN go mod download + +RUN CGO_ENABLED=0 GOOS=linux go build -o ./bin/eventindexer cmd/main.go + +FROM alpine:latest + +RUN apk add --no-cache ca-certificates + +COPY --from=builder /taiko-mono/packages/eventindexer/bin/eventindexer /usr/local/bin/ + +ENTRYPOINT ["eventindexer"] \ No newline at end of file diff --git a/packages/eventindexer/README.md b/packages/eventindexer/README.md new file mode 100644 index 0000000000..34ea0ee531 --- /dev/null +++ b/packages/eventindexer/README.md @@ -0,0 +1,12 @@ +[![Golang](https://github.com/taikoxyz/taiko-mono/actions/workflows/golang.yml/badge.svg)](https://github.com/taikoxyz/taiko-mono/actions/workflows/golang.yml) +[![Relayer](https://codecov.io/gh/taikoxyz/taiko-mono/branch/main/graph/badge.svg?token=E468X2PTJC&flag=relayer)](https://codecov.io/gh/taikoxyz/taiko-mono) + +# Indexer + +Catches events, stores them in the database to be queried via API. + +## Running the app + +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. + +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. diff --git a/packages/eventindexer/TaikoL1.json b/packages/eventindexer/TaikoL1.json new file mode 100644 index 0000000000..f04a963da2 --- /dev/null +++ b/packages/eventindexer/TaikoL1.json @@ -0,0 +1,1134 @@ +[ + { + "inputs": [], + "name": "L1_0_FEE_BASE", + "type": "error" + }, + { + "inputs": [], + "name": "L1_0_FEE_BASE", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ALREADY_PROVEN", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_CALLDATA", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_DEST", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_GAS_LIMIT", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_RECEIPT_ADDR", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_RECEIPT_DATA", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_RECEIPT_LOGS", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_RECEIPT_PROOF", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_RECEIPT_STATUS", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_RECEIPT_TOPICS", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_SIG_R", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_SIG_S", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_TX_PROOF", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ANCHOR_TYPE", + "type": "error" + }, + { + "inputs": [], + "name": "L1_BLOCK_NUMBER", + "type": "error" + }, + { + "inputs": [], + "name": "L1_BLOCK_NUMBER", + "type": "error" + }, + { + "inputs": [], + "name": "L1_CANNOT_BE_FIRST_PROVER", + "type": "error" + }, + { + "inputs": [], + "name": "L1_COMMITTED", + "type": "error" + }, + { + "inputs": [], + "name": "L1_CONFLICT_PROOF", + "type": "error" + }, + { + "inputs": [], + "name": "L1_CONTRACT_NOT_ALLOWED", + "type": "error" + }, + { + "inputs": [], + "name": "L1_DUP_PROVERS", + "type": "error" + }, + { + "inputs": [], + "name": "L1_EXTRA_DATA", + "type": "error" + }, + { + "inputs": [], + "name": "L1_GAS_LIMIT", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ID", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ID", + "type": "error" + }, + { + "inputs": [], + "name": "L1_INPUT_SIZE", + "type": "error" + }, + { + "inputs": [], + "name": "L1_INVALID_CONFIG", + "type": "error" + }, + { + "inputs": [], + "name": "L1_INVALID_CONFIG", + "type": "error" + }, + { + "inputs": [], + "name": "L1_INVALID_PARAM", + "type": "error" + }, + { + "inputs": [], + "name": "L1_METADATA_FIELD", + "type": "error" + }, + { + "inputs": [], + "name": "L1_META_MISMATCH", + "type": "error" + }, + { + "inputs": [], + "name": "L1_NOT_COMMITTED", + "type": "error" + }, + { + "inputs": [], + "name": "L1_NOT_ORACLE_PROVER", + "type": "error" + }, + { + "inputs": [], + "name": "L1_PROOF_LENGTH", + "type": "error" + }, + { + "inputs": [], + "name": "L1_PROVER", + "type": "error" + }, + { + "inputs": [], + "name": "L1_SOLO_PROPOSER", + "type": "error" + }, + { + "inputs": [], + "name": "L1_TOO_MANY_BLOCKS", + "type": "error" + }, + { + "inputs": [], + "name": "L1_TX_LIST", + "type": "error" + }, + { + "inputs": [], + "name": "L1_ZKP", + "type": "error" + }, + { + "inputs": [], + "name": "RESOLVER_DENIED", + "type": "error" + }, + { + "inputs": [], + "name": "RESOLVER_INVALID_ADDR", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "commitSlot", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "commitHash", + "type": "bytes32" + } + ], + "name": "BlockCommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "l1Height", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "l1Hash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txListHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "mixHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "gasLimit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "commitHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "commitSlot", + "type": "uint64" + } + ], + "indexed": false, + "internalType": "struct TaikoData.BlockMetadata", + "name": "meta", + "type": "tuple" + } + ], + "name": "BlockProposed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "parentHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "prover", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "provenAt", + "type": "uint64" + } + ], + "name": "BlockProven", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "name": "BlockVerified", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "srcHeight", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "srcHash", + "type": "bytes32" + } + ], + "name": "HeaderSynced", + "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": "uint64", + "name": "commitSlot", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "commitHash", + "type": "bytes32" + } + ], + "name": "commitBlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConfig", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumBlocks", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockHashHistory", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxVerificationsPerTx", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "commitConfirmations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockMaxGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxTransactionsPerBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxBytesPerTxList", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minTxGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "anchorTxGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "slotSmoothingFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardBurnBips", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposerDepositPctg", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeBaseMAF", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockTimeMAF", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofTimeMAF", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "rewardMultiplierPctg", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "feeGracePeriodPctg", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "feeMaxPeriodPctg", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "blockTimeCap", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proofTimeCap", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "bootstrapDiscountHalvingPeriod", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "enableTokenomics", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enablePublicInputsCheck", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableAnchorValidation", + "type": "bool" + } + ], + "internalType": "struct TaikoData.Config", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "parentHash", + "type": "bytes32" + } + ], + "name": "getForkChoice", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "blockHash", + "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": "getLatestSyncedHeader", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "provenAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposedAt", + "type": "uint64" + } + ], + "name": "getProofReward", + "outputs": [ + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getProposedBlock", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "metaHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deposit", + "type": "uint256" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "uint64", + "name": "proposedAt", + "type": "uint64" + } + ], + "internalType": "struct TaikoData.ProposedBlock", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getRewardBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStateVariables", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "feeBase", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "genesisHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "genesisTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "nextBlockId", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "lastProposedAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "avgBlockTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "latestVerifiedHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "latestVerifiedId", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "avgProofTime", + "type": "uint64" + } + ], + "internalType": "struct LibUtils.StateVariables", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "number", + "type": "uint256" + } + ], + "name": "getSyncedHeader", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addressManager", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_genesisBlockHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_feeBase", + "type": "uint256" + } + ], + "name": "init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "commitSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "commitHeight", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "commitHash", + "type": "bytes32" + } + ], + "name": "isCommitValid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "inputs", + "type": "bytes[]" + } + ], + "name": "proposeBlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockId", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "inputs", + "type": "bytes[]" + } + ], + "name": "proveBlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockId", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "inputs", + "type": "bytes[]" + } + ], + "name": "proveBlockInvalid", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "bool", + "name": "allowZeroAddress", + "type": "bool" + } + ], + "name": "resolve", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "bool", + "name": "allowZeroAddress", + "type": "bool" + } + ], + "name": "resolve", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "k", + "type": "uint8" + } + ], + "name": "signWithGoldenTouch", + "outputs": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "r", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "s", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "uint64", + "name": "genesisHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "genesisTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "__reservedA1", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "__reservedA2", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "feeBase", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "nextBlockId", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "lastProposedAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "avgBlockTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "__avgGasLimit", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "latestVerifiedHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "latestVerifiedId", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "avgProofTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "__reservedC1", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxBlocks", + "type": "uint256" + } + ], + "name": "verifyBlocks", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/packages/eventindexer/abigen.sh b/packages/eventindexer/abigen.sh new file mode 100755 index 0000000000..a048a6c1d3 --- /dev/null +++ b/packages/eventindexer/abigen.sh @@ -0,0 +1,22 @@ +#/bin/sh + +if [ ! -d "../protocol/artifacts" ]; then + echo "ABI not generated in protocol package yet. Please run npm install && npx hardhat compile in ../protocol" + exit 1 +fi + +paths=("L1/TaikoL1.sol") + +names=("TaikoL1") + +for (( i = 0; i < ${#paths[@]}; ++i )); +do + jq .abi ../protocol/artifacts/contracts/${paths[i]}/${names[i]}.json > ${names[i]}.json + lower=$(echo "${names[i]}" | tr '[:upper:]' '[:lower:]') + abigen --abi ${names[i]}.json \ + --pkg $lower \ + --type ${names[i]} \ + --out contracts/$lower/${names[i]}.go +done + +exit 0 diff --git a/packages/eventindexer/block.go b/packages/eventindexer/block.go new file mode 100644 index 0000000000..337141dec8 --- /dev/null +++ b/packages/eventindexer/block.go @@ -0,0 +1,31 @@ +package eventindexer + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Block is a database model representing simple header types +// to keep track of our most recently processed block number and hash. +type Block struct { + ID int `json:"id"` + Height uint64 `json:"blockHeight" gorm:"column:block_height"` + Hash string `json:"hash"` + ChainID int64 `json:"chainID"` +} + +// SaveBlockOpts is required to store a new block +type SaveBlockOpts struct { + Height uint64 + Hash common.Hash + ChainID *big.Int + EventName string +} + +// BlockRepository defines methods necessary for interacting with +// the block store. +type BlockRepository interface { + Save(opts SaveBlockOpts) error + GetLatestBlockProcessed(chainID *big.Int) (*Block, error) +} diff --git a/packages/eventindexer/caller.go b/packages/eventindexer/caller.go new file mode 100644 index 0000000000..59846dfc65 --- /dev/null +++ b/packages/eventindexer/caller.go @@ -0,0 +1,7 @@ +package eventindexer + +import "context" + +type Caller interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error +} diff --git a/packages/eventindexer/cli/cli.go b/packages/eventindexer/cli/cli.go new file mode 100644 index 0000000000..47e56afea5 --- /dev/null +++ b/packages/eventindexer/cli/cli.go @@ -0,0 +1,253 @@ +package cli + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/labstack/echo/v4" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/joho/godotenv" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/db" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/http" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/indexer" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/repo" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var ( + envVars = []string{ + "HTTP_PORT", + "L1_TAIKO_ADDRESS", + "L1_RPC_URL", + "MYSQL_USER", + "MYSQL_DATABASE", + "MYSQL_HOST", + "PROMETHEUS_HTTP_PORT", + } + + defaultBlockBatchSize = 2 + defaultSubscriptionBackoff = 600 * time.Second +) + +func Run( + mode eventindexer.Mode, + watchMode eventindexer.WatchMode, +) { + if err := loadAndValidateEnv(); err != nil { + log.Fatal(err) + } + + log.SetFormatter(&log.JSONFormatter{}) + + db, err := openDBConnection(eventindexer.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) (eventindexer.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) + } + + l1EthClient, err := ethclient.Dial(os.Getenv("L1_RPC_URL")) + if err != nil { + log.Fatal(err) + } + + srv, err := newHTTPServer(db, l1EthClient) + 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) + } + }() + + eventRepository, err := repo.NewEventRepository(db) + if err != nil { + log.Fatal(err) + } + + blockRepository, err := repo.NewBlockRepository(db) + if err != nil { + log.Fatal(err) + } + + blockBatchSize, err := strconv.Atoi(os.Getenv("BLOCK_BATCH_SIZE")) + if err != nil || blockBatchSize <= 0 { + blockBatchSize = defaultBlockBatchSize + } + + 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 + } + + l1RpcClient, err := rpc.DialContext(context.Background(), os.Getenv("L1_RPC_URL")) + if err != nil { + log.Fatal(err) + } + + i, err := indexer.NewService(indexer.NewServiceOpts{ + EventRepo: eventRepository, + BlockRepo: blockRepository, + EthClient: l1EthClient, + RPCClient: l1RpcClient, + SrcTaikoAddress: common.HexToAddress(os.Getenv("L1_TAIKO_ADDRESS")), + BlockBatchSize: uint64(blockBatchSize), + SubscriptionBackoff: subscriptionBackoff, + }) + if err != nil { + log.Fatal(err) + } + + go func() { + if err := i.FilterThenSubscribe(context.Background(), mode, watchMode); err != nil { + log.Fatal(err) + } + }() + + closeFunc := func() { + l1EthClient.Close() + l1RpcClient.Close() + } + + defer closeFunc() + + <-forever +} + +func openDBConnection(opts eventindexer.DBConnectionOpts) (eventindexer.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 eventindexer.DB, l1EthClient *ethclient.Client) (*http.Server, error) { + eventRepo, err := repo.NewEventRepository(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"), ","), + }) + if err != nil { + return nil, err + } + + return srv, nil +} diff --git a/packages/eventindexer/cmd/main.go b/packages/eventindexer/cmd/main.go new file mode 100644 index 0000000000..96f89836c0 --- /dev/null +++ b/packages/eventindexer/cmd/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "flag" + + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/cli" +) + +func main() { + modePtr := flag.String("mode", string(eventindexer.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 + `) + + watchModePtr := flag.String("watch-mode", string(eventindexer.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 + `) + + flag.Parse() + + cli.Run( + eventindexer.Mode(*modePtr), + eventindexer.WatchMode(*watchModePtr), + ) +} diff --git a/packages/eventindexer/contracts/taikol1/TaikoL1.go b/packages/eventindexer/contracts/taikol1/TaikoL1.go new file mode 100644 index 0000000000..536a40aa63 --- /dev/null +++ b/packages/eventindexer/contracts/taikol1/TaikoL1.go @@ -0,0 +1,2031 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package taikol1 + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// LibUtilsStateVariables is an auto generated low-level Go binding around an user-defined struct. +type LibUtilsStateVariables struct { + FeeBase *big.Int + GenesisHeight uint64 + GenesisTimestamp uint64 + NextBlockId uint64 + LastProposedAt uint64 + AvgBlockTime uint64 + LatestVerifiedHeight uint64 + LatestVerifiedId uint64 + AvgProofTime uint64 +} + +// TaikoDataBlockMetadata is an auto generated low-level Go binding around an user-defined struct. +type TaikoDataBlockMetadata struct { + Id *big.Int + L1Height *big.Int + L1Hash [32]byte + Beneficiary common.Address + TxListHash [32]byte + MixHash [32]byte + ExtraData []byte + GasLimit uint64 + Timestamp uint64 + CommitHeight uint64 + CommitSlot uint64 +} + +// TaikoDataConfig is an auto generated low-level Go binding around an user-defined struct. +type TaikoDataConfig struct { + ChainId *big.Int + MaxNumBlocks *big.Int + BlockHashHistory *big.Int + MaxVerificationsPerTx *big.Int + CommitConfirmations *big.Int + BlockMaxGasLimit *big.Int + MaxTransactionsPerBlock *big.Int + MaxBytesPerTxList *big.Int + MinTxGasLimit *big.Int + AnchorTxGasLimit *big.Int + SlotSmoothingFactor *big.Int + RewardBurnBips *big.Int + ProposerDepositPctg *big.Int + FeeBaseMAF *big.Int + BlockTimeMAF *big.Int + ProofTimeMAF *big.Int + RewardMultiplierPctg uint64 + FeeGracePeriodPctg uint64 + FeeMaxPeriodPctg uint64 + BlockTimeCap uint64 + ProofTimeCap uint64 + BootstrapDiscountHalvingPeriod uint64 + EnableTokenomics bool + EnablePublicInputsCheck bool + EnableAnchorValidation bool +} + +// TaikoDataForkChoice is an auto generated low-level Go binding around an user-defined struct. +type TaikoDataForkChoice struct { + BlockHash [32]byte + Prover common.Address + ProvenAt uint64 +} + +// TaikoDataProposedBlock is an auto generated low-level Go binding around an user-defined struct. +type TaikoDataProposedBlock struct { + MetaHash [32]byte + Deposit *big.Int + Proposer common.Address + ProposedAt uint64 +} + +// TaikoL1MetaData contains all meta data concerning the TaikoL1 contract. +var TaikoL1MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"name\":\"L1_0_FEE_BASE\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_0_FEE_BASE\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ALREADY_PROVEN\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_CALLDATA\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_DEST\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_GAS_LIMIT\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_RECEIPT_ADDR\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_RECEIPT_DATA\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_RECEIPT_LOGS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_RECEIPT_PROOF\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_RECEIPT_STATUS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_RECEIPT_TOPICS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_SIG_R\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_SIG_S\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_TX_PROOF\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ANCHOR_TYPE\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_BLOCK_NUMBER\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_BLOCK_NUMBER\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_CANNOT_BE_FIRST_PROVER\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_COMMITTED\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_CONFLICT_PROOF\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_CONTRACT_NOT_ALLOWED\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_DUP_PROVERS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_EXTRA_DATA\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_GAS_LIMIT\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ID\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ID\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_INPUT_SIZE\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_INVALID_CONFIG\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_INVALID_CONFIG\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_INVALID_PARAM\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_METADATA_FIELD\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_META_MISMATCH\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_NOT_COMMITTED\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_NOT_ORACLE_PROVER\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_PROOF_LENGTH\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_PROVER\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_SOLO_PROPOSER\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_TOO_MANY_BLOCKS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_TX_LIST\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"L1_ZKP\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RESOLVER_DENIED\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RESOLVER_INVALID_ADDR\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"commitSlot\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"commitHash\",\"type\":\"bytes32\"}],\"name\":\"BlockCommitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"l1Height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"l1Hash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"beneficiary\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"txListHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"mixHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"timestamp\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"commitHeight\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"commitSlot\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structTaikoData.BlockMetadata\",\"name\":\"meta\",\"type\":\"tuple\"}],\"name\":\"BlockProposed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"prover\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"provenAt\",\"type\":\"uint64\"}],\"name\":\"BlockProven\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"name\":\"BlockVerified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"srcHeight\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"srcHash\",\"type\":\"bytes32\"}],\"name\":\"HeaderSynced\",\"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\":\"uint64\",\"name\":\"commitSlot\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"commitHash\",\"type\":\"bytes32\"}],\"name\":\"commitBlock\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBlockFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxNumBlocks\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blockHashHistory\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxVerificationsPerTx\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"commitConfirmations\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blockMaxGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxTransactionsPerBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxBytesPerTxList\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minTxGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"anchorTxGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"slotSmoothingFactor\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"rewardBurnBips\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"proposerDepositPctg\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeBaseMAF\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blockTimeMAF\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"proofTimeMAF\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"rewardMultiplierPctg\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"feeGracePeriodPctg\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"feeMaxPeriodPctg\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"blockTimeCap\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"proofTimeCap\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"bootstrapDiscountHalvingPeriod\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"enableTokenomics\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"enablePublicInputsCheck\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"enableAnchorValidation\",\"type\":\"bool\"}],\"internalType\":\"structTaikoData.Config\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"}],\"name\":\"getForkChoice\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"prover\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"provenAt\",\"type\":\"uint64\"}],\"internalType\":\"structTaikoData.ForkChoice\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLatestSyncedHeader\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"provenAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"proposedAt\",\"type\":\"uint64\"}],\"name\":\"getProofReward\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"reward\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"getProposedBlock\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"metaHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"deposit\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"proposedAt\",\"type\":\"uint64\"}],\"internalType\":\"structTaikoData.ProposedBlock\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getRewardBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getStateVariables\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"feeBase\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"genesisHeight\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"genesisTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nextBlockId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"lastProposedAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"avgBlockTime\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"latestVerifiedHeight\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"latestVerifiedId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"avgProofTime\",\"type\":\"uint64\"}],\"internalType\":\"structLibUtils.StateVariables\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"number\",\"type\":\"uint256\"}],\"name\":\"getSyncedHeader\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addressManager\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"_genesisBlockHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_feeBase\",\"type\":\"uint256\"}],\"name\":\"init\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"commitSlot\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"commitHeight\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitHash\",\"type\":\"bytes32\"}],\"name\":\"isCommitValid\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"inputs\",\"type\":\"bytes[]\"}],\"name\":\"proposeBlock\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockId\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"inputs\",\"type\":\"bytes[]\"}],\"name\":\"proveBlock\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockId\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"inputs\",\"type\":\"bytes[]\"}],\"name\":\"proveBlockInvalid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"allowZeroAddress\",\"type\":\"bool\"}],\"name\":\"resolve\",\"outputs\":[{\"internalType\":\"addresspayable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"allowZeroAddress\",\"type\":\"bool\"}],\"name\":\"resolve\",\"outputs\":[{\"internalType\":\"addresspayable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"k\",\"type\":\"uint8\"}],\"name\":\"signWithGoldenTouch\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"r\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"s\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"state\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"genesisHeight\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"genesisTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"__reservedA1\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"__reservedA2\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"feeBase\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"nextBlockId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"lastProposedAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"avgBlockTime\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"__avgGasLimit\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"latestVerifiedHeight\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"latestVerifiedId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"avgProofTime\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"__reservedC1\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"maxBlocks\",\"type\":\"uint256\"}],\"name\":\"verifyBlocks\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawBalance\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", +} + +// TaikoL1ABI is the input ABI used to generate the binding from. +// Deprecated: Use TaikoL1MetaData.ABI instead. +var TaikoL1ABI = TaikoL1MetaData.ABI + +// TaikoL1 is an auto generated Go binding around an Ethereum contract. +type TaikoL1 struct { + TaikoL1Caller // Read-only binding to the contract + TaikoL1Transactor // Write-only binding to the contract + TaikoL1Filterer // Log filterer for contract events +} + +// TaikoL1Caller is an auto generated read-only Go binding around an Ethereum contract. +type TaikoL1Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TaikoL1Transactor is an auto generated write-only Go binding around an Ethereum contract. +type TaikoL1Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TaikoL1Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TaikoL1Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TaikoL1Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TaikoL1Session struct { + Contract *TaikoL1 // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TaikoL1CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TaikoL1CallerSession struct { + Contract *TaikoL1Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TaikoL1TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TaikoL1TransactorSession struct { + Contract *TaikoL1Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TaikoL1Raw is an auto generated low-level Go binding around an Ethereum contract. +type TaikoL1Raw struct { + Contract *TaikoL1 // Generic contract binding to access the raw methods on +} + +// TaikoL1CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TaikoL1CallerRaw struct { + Contract *TaikoL1Caller // Generic read-only contract binding to access the raw methods on +} + +// TaikoL1TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TaikoL1TransactorRaw struct { + Contract *TaikoL1Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewTaikoL1 creates a new instance of TaikoL1, bound to a specific deployed contract. +func NewTaikoL1(address common.Address, backend bind.ContractBackend) (*TaikoL1, error) { + contract, err := bindTaikoL1(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &TaikoL1{TaikoL1Caller: TaikoL1Caller{contract: contract}, TaikoL1Transactor: TaikoL1Transactor{contract: contract}, TaikoL1Filterer: TaikoL1Filterer{contract: contract}}, nil +} + +// NewTaikoL1Caller creates a new read-only instance of TaikoL1, bound to a specific deployed contract. +func NewTaikoL1Caller(address common.Address, caller bind.ContractCaller) (*TaikoL1Caller, error) { + contract, err := bindTaikoL1(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TaikoL1Caller{contract: contract}, nil +} + +// NewTaikoL1Transactor creates a new write-only instance of TaikoL1, bound to a specific deployed contract. +func NewTaikoL1Transactor(address common.Address, transactor bind.ContractTransactor) (*TaikoL1Transactor, error) { + contract, err := bindTaikoL1(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TaikoL1Transactor{contract: contract}, nil +} + +// NewTaikoL1Filterer creates a new log filterer instance of TaikoL1, bound to a specific deployed contract. +func NewTaikoL1Filterer(address common.Address, filterer bind.ContractFilterer) (*TaikoL1Filterer, error) { + contract, err := bindTaikoL1(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TaikoL1Filterer{contract: contract}, nil +} + +// bindTaikoL1 binds a generic wrapper to an already deployed contract. +func bindTaikoL1(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := TaikoL1MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_TaikoL1 *TaikoL1Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TaikoL1.Contract.TaikoL1Caller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_TaikoL1 *TaikoL1Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TaikoL1.Contract.TaikoL1Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TaikoL1 *TaikoL1Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TaikoL1.Contract.TaikoL1Transactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_TaikoL1 *TaikoL1CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TaikoL1.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_TaikoL1 *TaikoL1TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TaikoL1.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TaikoL1 *TaikoL1TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TaikoL1.Contract.contract.Transact(opts, method, params...) +} + +// AddressManager is a free data retrieval call binding the contract method 0x3ab76e9f. +// +// Solidity: function addressManager() view returns(address) +func (_TaikoL1 *TaikoL1Caller) AddressManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "addressManager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// AddressManager is a free data retrieval call binding the contract method 0x3ab76e9f. +// +// Solidity: function addressManager() view returns(address) +func (_TaikoL1 *TaikoL1Session) AddressManager() (common.Address, error) { + return _TaikoL1.Contract.AddressManager(&_TaikoL1.CallOpts) +} + +// AddressManager is a free data retrieval call binding the contract method 0x3ab76e9f. +// +// Solidity: function addressManager() view returns(address) +func (_TaikoL1 *TaikoL1CallerSession) AddressManager() (common.Address, error) { + return _TaikoL1.Contract.AddressManager(&_TaikoL1.CallOpts) +} + +// GetBlockFee is a free data retrieval call binding the contract method 0x7baf0bc7. +// +// Solidity: function getBlockFee() view returns(uint256) +func (_TaikoL1 *TaikoL1Caller) GetBlockFee(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getBlockFee") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetBlockFee is a free data retrieval call binding the contract method 0x7baf0bc7. +// +// Solidity: function getBlockFee() view returns(uint256) +func (_TaikoL1 *TaikoL1Session) GetBlockFee() (*big.Int, error) { + return _TaikoL1.Contract.GetBlockFee(&_TaikoL1.CallOpts) +} + +// GetBlockFee is a free data retrieval call binding the contract method 0x7baf0bc7. +// +// Solidity: function getBlockFee() view returns(uint256) +func (_TaikoL1 *TaikoL1CallerSession) GetBlockFee() (*big.Int, error) { + return _TaikoL1.Contract.GetBlockFee(&_TaikoL1.CallOpts) +} + +// GetConfig is a free data retrieval call binding the contract method 0xc3f909d4. +// +// Solidity: function getConfig() pure returns((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint64,uint64,uint64,uint64,uint64,uint64,bool,bool,bool)) +func (_TaikoL1 *TaikoL1Caller) GetConfig(opts *bind.CallOpts) (TaikoDataConfig, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getConfig") + + if err != nil { + return *new(TaikoDataConfig), err + } + + out0 := *abi.ConvertType(out[0], new(TaikoDataConfig)).(*TaikoDataConfig) + + return out0, err + +} + +// GetConfig is a free data retrieval call binding the contract method 0xc3f909d4. +// +// Solidity: function getConfig() pure returns((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint64,uint64,uint64,uint64,uint64,uint64,bool,bool,bool)) +func (_TaikoL1 *TaikoL1Session) GetConfig() (TaikoDataConfig, error) { + return _TaikoL1.Contract.GetConfig(&_TaikoL1.CallOpts) +} + +// GetConfig is a free data retrieval call binding the contract method 0xc3f909d4. +// +// Solidity: function getConfig() pure returns((uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint64,uint64,uint64,uint64,uint64,uint64,bool,bool,bool)) +func (_TaikoL1 *TaikoL1CallerSession) GetConfig() (TaikoDataConfig, error) { + return _TaikoL1.Contract.GetConfig(&_TaikoL1.CallOpts) +} + +// GetForkChoice is a free data retrieval call binding the contract method 0xe00ea1e1. +// +// Solidity: function getForkChoice(uint256 id, bytes32 parentHash) view returns((bytes32,address,uint64)) +func (_TaikoL1 *TaikoL1Caller) GetForkChoice(opts *bind.CallOpts, id *big.Int, parentHash [32]byte) (TaikoDataForkChoice, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getForkChoice", id, parentHash) + + if err != nil { + return *new(TaikoDataForkChoice), err + } + + out0 := *abi.ConvertType(out[0], new(TaikoDataForkChoice)).(*TaikoDataForkChoice) + + return out0, err + +} + +// GetForkChoice is a free data retrieval call binding the contract method 0xe00ea1e1. +// +// Solidity: function getForkChoice(uint256 id, bytes32 parentHash) view returns((bytes32,address,uint64)) +func (_TaikoL1 *TaikoL1Session) GetForkChoice(id *big.Int, parentHash [32]byte) (TaikoDataForkChoice, error) { + return _TaikoL1.Contract.GetForkChoice(&_TaikoL1.CallOpts, id, parentHash) +} + +// GetForkChoice is a free data retrieval call binding the contract method 0xe00ea1e1. +// +// Solidity: function getForkChoice(uint256 id, bytes32 parentHash) view returns((bytes32,address,uint64)) +func (_TaikoL1 *TaikoL1CallerSession) GetForkChoice(id *big.Int, parentHash [32]byte) (TaikoDataForkChoice, error) { + return _TaikoL1.Contract.GetForkChoice(&_TaikoL1.CallOpts, id, parentHash) +} + +// GetLatestSyncedHeader is a free data retrieval call binding the contract method 0x5155ce9f. +// +// Solidity: function getLatestSyncedHeader() view returns(bytes32) +func (_TaikoL1 *TaikoL1Caller) GetLatestSyncedHeader(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getLatestSyncedHeader") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetLatestSyncedHeader is a free data retrieval call binding the contract method 0x5155ce9f. +// +// Solidity: function getLatestSyncedHeader() view returns(bytes32) +func (_TaikoL1 *TaikoL1Session) GetLatestSyncedHeader() ([32]byte, error) { + return _TaikoL1.Contract.GetLatestSyncedHeader(&_TaikoL1.CallOpts) +} + +// GetLatestSyncedHeader is a free data retrieval call binding the contract method 0x5155ce9f. +// +// Solidity: function getLatestSyncedHeader() view returns(bytes32) +func (_TaikoL1 *TaikoL1CallerSession) GetLatestSyncedHeader() ([32]byte, error) { + return _TaikoL1.Contract.GetLatestSyncedHeader(&_TaikoL1.CallOpts) +} + +// GetProofReward is a free data retrieval call binding the contract method 0x4ee56f9e. +// +// Solidity: function getProofReward(uint64 provenAt, uint64 proposedAt) view returns(uint256 reward) +func (_TaikoL1 *TaikoL1Caller) GetProofReward(opts *bind.CallOpts, provenAt uint64, proposedAt uint64) (*big.Int, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getProofReward", provenAt, proposedAt) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetProofReward is a free data retrieval call binding the contract method 0x4ee56f9e. +// +// Solidity: function getProofReward(uint64 provenAt, uint64 proposedAt) view returns(uint256 reward) +func (_TaikoL1 *TaikoL1Session) GetProofReward(provenAt uint64, proposedAt uint64) (*big.Int, error) { + return _TaikoL1.Contract.GetProofReward(&_TaikoL1.CallOpts, provenAt, proposedAt) +} + +// GetProofReward is a free data retrieval call binding the contract method 0x4ee56f9e. +// +// Solidity: function getProofReward(uint64 provenAt, uint64 proposedAt) view returns(uint256 reward) +func (_TaikoL1 *TaikoL1CallerSession) GetProofReward(provenAt uint64, proposedAt uint64) (*big.Int, error) { + return _TaikoL1.Contract.GetProofReward(&_TaikoL1.CallOpts, provenAt, proposedAt) +} + +// GetProposedBlock is a free data retrieval call binding the contract method 0x8972b10c. +// +// Solidity: function getProposedBlock(uint256 id) view returns((bytes32,uint256,address,uint64)) +func (_TaikoL1 *TaikoL1Caller) GetProposedBlock(opts *bind.CallOpts, id *big.Int) (TaikoDataProposedBlock, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getProposedBlock", id) + + if err != nil { + return *new(TaikoDataProposedBlock), err + } + + out0 := *abi.ConvertType(out[0], new(TaikoDataProposedBlock)).(*TaikoDataProposedBlock) + + return out0, err + +} + +// GetProposedBlock is a free data retrieval call binding the contract method 0x8972b10c. +// +// Solidity: function getProposedBlock(uint256 id) view returns((bytes32,uint256,address,uint64)) +func (_TaikoL1 *TaikoL1Session) GetProposedBlock(id *big.Int) (TaikoDataProposedBlock, error) { + return _TaikoL1.Contract.GetProposedBlock(&_TaikoL1.CallOpts, id) +} + +// GetProposedBlock is a free data retrieval call binding the contract method 0x8972b10c. +// +// Solidity: function getProposedBlock(uint256 id) view returns((bytes32,uint256,address,uint64)) +func (_TaikoL1 *TaikoL1CallerSession) GetProposedBlock(id *big.Int) (TaikoDataProposedBlock, error) { + return _TaikoL1.Contract.GetProposedBlock(&_TaikoL1.CallOpts, id) +} + +// GetRewardBalance is a free data retrieval call binding the contract method 0xd5a849e9. +// +// Solidity: function getRewardBalance(address addr) view returns(uint256) +func (_TaikoL1 *TaikoL1Caller) GetRewardBalance(opts *bind.CallOpts, addr common.Address) (*big.Int, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getRewardBalance", addr) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetRewardBalance is a free data retrieval call binding the contract method 0xd5a849e9. +// +// Solidity: function getRewardBalance(address addr) view returns(uint256) +func (_TaikoL1 *TaikoL1Session) GetRewardBalance(addr common.Address) (*big.Int, error) { + return _TaikoL1.Contract.GetRewardBalance(&_TaikoL1.CallOpts, addr) +} + +// GetRewardBalance is a free data retrieval call binding the contract method 0xd5a849e9. +// +// Solidity: function getRewardBalance(address addr) view returns(uint256) +func (_TaikoL1 *TaikoL1CallerSession) GetRewardBalance(addr common.Address) (*big.Int, error) { + return _TaikoL1.Contract.GetRewardBalance(&_TaikoL1.CallOpts, addr) +} + +// GetStateVariables is a free data retrieval call binding the contract method 0xdde89cf5. +// +// Solidity: function getStateVariables() view returns((uint256,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)) +func (_TaikoL1 *TaikoL1Caller) GetStateVariables(opts *bind.CallOpts) (LibUtilsStateVariables, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getStateVariables") + + if err != nil { + return *new(LibUtilsStateVariables), err + } + + out0 := *abi.ConvertType(out[0], new(LibUtilsStateVariables)).(*LibUtilsStateVariables) + + return out0, err + +} + +// GetStateVariables is a free data retrieval call binding the contract method 0xdde89cf5. +// +// Solidity: function getStateVariables() view returns((uint256,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)) +func (_TaikoL1 *TaikoL1Session) GetStateVariables() (LibUtilsStateVariables, error) { + return _TaikoL1.Contract.GetStateVariables(&_TaikoL1.CallOpts) +} + +// GetStateVariables is a free data retrieval call binding the contract method 0xdde89cf5. +// +// Solidity: function getStateVariables() view returns((uint256,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64)) +func (_TaikoL1 *TaikoL1CallerSession) GetStateVariables() (LibUtilsStateVariables, error) { + return _TaikoL1.Contract.GetStateVariables(&_TaikoL1.CallOpts) +} + +// GetSyncedHeader is a free data retrieval call binding the contract method 0x25bf86f2. +// +// Solidity: function getSyncedHeader(uint256 number) view returns(bytes32) +func (_TaikoL1 *TaikoL1Caller) GetSyncedHeader(opts *bind.CallOpts, number *big.Int) ([32]byte, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "getSyncedHeader", number) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetSyncedHeader is a free data retrieval call binding the contract method 0x25bf86f2. +// +// Solidity: function getSyncedHeader(uint256 number) view returns(bytes32) +func (_TaikoL1 *TaikoL1Session) GetSyncedHeader(number *big.Int) ([32]byte, error) { + return _TaikoL1.Contract.GetSyncedHeader(&_TaikoL1.CallOpts, number) +} + +// GetSyncedHeader is a free data retrieval call binding the contract method 0x25bf86f2. +// +// Solidity: function getSyncedHeader(uint256 number) view returns(bytes32) +func (_TaikoL1 *TaikoL1CallerSession) GetSyncedHeader(number *big.Int) ([32]byte, error) { + return _TaikoL1.Contract.GetSyncedHeader(&_TaikoL1.CallOpts, number) +} + +// IsCommitValid is a free data retrieval call binding the contract method 0x340d9599. +// +// Solidity: function isCommitValid(uint256 commitSlot, uint256 commitHeight, bytes32 commitHash) view returns(bool) +func (_TaikoL1 *TaikoL1Caller) IsCommitValid(opts *bind.CallOpts, commitSlot *big.Int, commitHeight *big.Int, commitHash [32]byte) (bool, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "isCommitValid", commitSlot, commitHeight, commitHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsCommitValid is a free data retrieval call binding the contract method 0x340d9599. +// +// Solidity: function isCommitValid(uint256 commitSlot, uint256 commitHeight, bytes32 commitHash) view returns(bool) +func (_TaikoL1 *TaikoL1Session) IsCommitValid(commitSlot *big.Int, commitHeight *big.Int, commitHash [32]byte) (bool, error) { + return _TaikoL1.Contract.IsCommitValid(&_TaikoL1.CallOpts, commitSlot, commitHeight, commitHash) +} + +// IsCommitValid is a free data retrieval call binding the contract method 0x340d9599. +// +// Solidity: function isCommitValid(uint256 commitSlot, uint256 commitHeight, bytes32 commitHash) view returns(bool) +func (_TaikoL1 *TaikoL1CallerSession) IsCommitValid(commitSlot *big.Int, commitHeight *big.Int, commitHash [32]byte) (bool, error) { + return _TaikoL1.Contract.IsCommitValid(&_TaikoL1.CallOpts, commitSlot, commitHeight, commitHash) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_TaikoL1 *TaikoL1Caller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_TaikoL1 *TaikoL1Session) Owner() (common.Address, error) { + return _TaikoL1.Contract.Owner(&_TaikoL1.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_TaikoL1 *TaikoL1CallerSession) Owner() (common.Address, error) { + return _TaikoL1.Contract.Owner(&_TaikoL1.CallOpts) +} + +// Resolve is a free data retrieval call binding the contract method 0x0ca4dffd. +// +// Solidity: function resolve(string name, bool allowZeroAddress) view returns(address) +func (_TaikoL1 *TaikoL1Caller) Resolve(opts *bind.CallOpts, name string, allowZeroAddress bool) (common.Address, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "resolve", name, allowZeroAddress) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Resolve is a free data retrieval call binding the contract method 0x0ca4dffd. +// +// Solidity: function resolve(string name, bool allowZeroAddress) view returns(address) +func (_TaikoL1 *TaikoL1Session) Resolve(name string, allowZeroAddress bool) (common.Address, error) { + return _TaikoL1.Contract.Resolve(&_TaikoL1.CallOpts, name, allowZeroAddress) +} + +// Resolve is a free data retrieval call binding the contract method 0x0ca4dffd. +// +// Solidity: function resolve(string name, bool allowZeroAddress) view returns(address) +func (_TaikoL1 *TaikoL1CallerSession) Resolve(name string, allowZeroAddress bool) (common.Address, error) { + return _TaikoL1.Contract.Resolve(&_TaikoL1.CallOpts, name, allowZeroAddress) +} + +// Resolve0 is a free data retrieval call binding the contract method 0x1be2bfa7. +// +// Solidity: function resolve(uint256 chainId, string name, bool allowZeroAddress) view returns(address) +func (_TaikoL1 *TaikoL1Caller) Resolve0(opts *bind.CallOpts, chainId *big.Int, name string, allowZeroAddress bool) (common.Address, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "resolve0", chainId, name, allowZeroAddress) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Resolve0 is a free data retrieval call binding the contract method 0x1be2bfa7. +// +// Solidity: function resolve(uint256 chainId, string name, bool allowZeroAddress) view returns(address) +func (_TaikoL1 *TaikoL1Session) Resolve0(chainId *big.Int, name string, allowZeroAddress bool) (common.Address, error) { + return _TaikoL1.Contract.Resolve0(&_TaikoL1.CallOpts, chainId, name, allowZeroAddress) +} + +// Resolve0 is a free data retrieval call binding the contract method 0x1be2bfa7. +// +// Solidity: function resolve(uint256 chainId, string name, bool allowZeroAddress) view returns(address) +func (_TaikoL1 *TaikoL1CallerSession) Resolve0(chainId *big.Int, name string, allowZeroAddress bool) (common.Address, error) { + return _TaikoL1.Contract.Resolve0(&_TaikoL1.CallOpts, chainId, name, allowZeroAddress) +} + +// SignWithGoldenTouch is a free data retrieval call binding the contract method 0xdadec12a. +// +// Solidity: function signWithGoldenTouch(bytes32 hash, uint8 k) view returns(uint8 v, uint256 r, uint256 s) +func (_TaikoL1 *TaikoL1Caller) SignWithGoldenTouch(opts *bind.CallOpts, hash [32]byte, k uint8) (struct { + V uint8 + R *big.Int + S *big.Int +}, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "signWithGoldenTouch", hash, k) + + outstruct := new(struct { + V uint8 + R *big.Int + S *big.Int + }) + if err != nil { + return *outstruct, err + } + + outstruct.V = *abi.ConvertType(out[0], new(uint8)).(*uint8) + outstruct.R = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.S = *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +// SignWithGoldenTouch is a free data retrieval call binding the contract method 0xdadec12a. +// +// Solidity: function signWithGoldenTouch(bytes32 hash, uint8 k) view returns(uint8 v, uint256 r, uint256 s) +func (_TaikoL1 *TaikoL1Session) SignWithGoldenTouch(hash [32]byte, k uint8) (struct { + V uint8 + R *big.Int + S *big.Int +}, error) { + return _TaikoL1.Contract.SignWithGoldenTouch(&_TaikoL1.CallOpts, hash, k) +} + +// SignWithGoldenTouch is a free data retrieval call binding the contract method 0xdadec12a. +// +// Solidity: function signWithGoldenTouch(bytes32 hash, uint8 k) view returns(uint8 v, uint256 r, uint256 s) +func (_TaikoL1 *TaikoL1CallerSession) SignWithGoldenTouch(hash [32]byte, k uint8) (struct { + V uint8 + R *big.Int + S *big.Int +}, error) { + return _TaikoL1.Contract.SignWithGoldenTouch(&_TaikoL1.CallOpts, hash, k) +} + +// State is a free data retrieval call binding the contract method 0xc19d93fb. +// +// Solidity: function state() view returns(uint64 genesisHeight, uint64 genesisTimestamp, uint64 __reservedA1, uint64 __reservedA2, uint256 feeBase, uint64 nextBlockId, uint64 lastProposedAt, uint64 avgBlockTime, uint64 __avgGasLimit, uint64 latestVerifiedHeight, uint64 latestVerifiedId, uint64 avgProofTime, uint64 __reservedC1) +func (_TaikoL1 *TaikoL1Caller) State(opts *bind.CallOpts) (struct { + GenesisHeight uint64 + GenesisTimestamp uint64 + ReservedA1 uint64 + ReservedA2 uint64 + FeeBase *big.Int + NextBlockId uint64 + LastProposedAt uint64 + AvgBlockTime uint64 + AvgGasLimit uint64 + LatestVerifiedHeight uint64 + LatestVerifiedId uint64 + AvgProofTime uint64 + ReservedC1 uint64 +}, error) { + var out []interface{} + err := _TaikoL1.contract.Call(opts, &out, "state") + + outstruct := new(struct { + GenesisHeight uint64 + GenesisTimestamp uint64 + ReservedA1 uint64 + ReservedA2 uint64 + FeeBase *big.Int + NextBlockId uint64 + LastProposedAt uint64 + AvgBlockTime uint64 + AvgGasLimit uint64 + LatestVerifiedHeight uint64 + LatestVerifiedId uint64 + AvgProofTime uint64 + ReservedC1 uint64 + }) + if err != nil { + return *outstruct, err + } + + outstruct.GenesisHeight = *abi.ConvertType(out[0], new(uint64)).(*uint64) + outstruct.GenesisTimestamp = *abi.ConvertType(out[1], new(uint64)).(*uint64) + outstruct.ReservedA1 = *abi.ConvertType(out[2], new(uint64)).(*uint64) + outstruct.ReservedA2 = *abi.ConvertType(out[3], new(uint64)).(*uint64) + outstruct.FeeBase = *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + outstruct.NextBlockId = *abi.ConvertType(out[5], new(uint64)).(*uint64) + outstruct.LastProposedAt = *abi.ConvertType(out[6], new(uint64)).(*uint64) + outstruct.AvgBlockTime = *abi.ConvertType(out[7], new(uint64)).(*uint64) + outstruct.AvgGasLimit = *abi.ConvertType(out[8], new(uint64)).(*uint64) + outstruct.LatestVerifiedHeight = *abi.ConvertType(out[9], new(uint64)).(*uint64) + outstruct.LatestVerifiedId = *abi.ConvertType(out[10], new(uint64)).(*uint64) + outstruct.AvgProofTime = *abi.ConvertType(out[11], new(uint64)).(*uint64) + outstruct.ReservedC1 = *abi.ConvertType(out[12], new(uint64)).(*uint64) + + return *outstruct, err + +} + +// State is a free data retrieval call binding the contract method 0xc19d93fb. +// +// Solidity: function state() view returns(uint64 genesisHeight, uint64 genesisTimestamp, uint64 __reservedA1, uint64 __reservedA2, uint256 feeBase, uint64 nextBlockId, uint64 lastProposedAt, uint64 avgBlockTime, uint64 __avgGasLimit, uint64 latestVerifiedHeight, uint64 latestVerifiedId, uint64 avgProofTime, uint64 __reservedC1) +func (_TaikoL1 *TaikoL1Session) State() (struct { + GenesisHeight uint64 + GenesisTimestamp uint64 + ReservedA1 uint64 + ReservedA2 uint64 + FeeBase *big.Int + NextBlockId uint64 + LastProposedAt uint64 + AvgBlockTime uint64 + AvgGasLimit uint64 + LatestVerifiedHeight uint64 + LatestVerifiedId uint64 + AvgProofTime uint64 + ReservedC1 uint64 +}, error) { + return _TaikoL1.Contract.State(&_TaikoL1.CallOpts) +} + +// State is a free data retrieval call binding the contract method 0xc19d93fb. +// +// Solidity: function state() view returns(uint64 genesisHeight, uint64 genesisTimestamp, uint64 __reservedA1, uint64 __reservedA2, uint256 feeBase, uint64 nextBlockId, uint64 lastProposedAt, uint64 avgBlockTime, uint64 __avgGasLimit, uint64 latestVerifiedHeight, uint64 latestVerifiedId, uint64 avgProofTime, uint64 __reservedC1) +func (_TaikoL1 *TaikoL1CallerSession) State() (struct { + GenesisHeight uint64 + GenesisTimestamp uint64 + ReservedA1 uint64 + ReservedA2 uint64 + FeeBase *big.Int + NextBlockId uint64 + LastProposedAt uint64 + AvgBlockTime uint64 + AvgGasLimit uint64 + LatestVerifiedHeight uint64 + LatestVerifiedId uint64 + AvgProofTime uint64 + ReservedC1 uint64 +}, error) { + return _TaikoL1.Contract.State(&_TaikoL1.CallOpts) +} + +// CommitBlock is a paid mutator transaction binding the contract method 0x7e7a262c. +// +// Solidity: function commitBlock(uint64 commitSlot, bytes32 commitHash) returns() +func (_TaikoL1 *TaikoL1Transactor) CommitBlock(opts *bind.TransactOpts, commitSlot uint64, commitHash [32]byte) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "commitBlock", commitSlot, commitHash) +} + +// CommitBlock is a paid mutator transaction binding the contract method 0x7e7a262c. +// +// Solidity: function commitBlock(uint64 commitSlot, bytes32 commitHash) returns() +func (_TaikoL1 *TaikoL1Session) CommitBlock(commitSlot uint64, commitHash [32]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.CommitBlock(&_TaikoL1.TransactOpts, commitSlot, commitHash) +} + +// CommitBlock is a paid mutator transaction binding the contract method 0x7e7a262c. +// +// Solidity: function commitBlock(uint64 commitSlot, bytes32 commitHash) returns() +func (_TaikoL1 *TaikoL1TransactorSession) CommitBlock(commitSlot uint64, commitHash [32]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.CommitBlock(&_TaikoL1.TransactOpts, commitSlot, commitHash) +} + +// Init is a paid mutator transaction binding the contract method 0x9c5e9f06. +// +// Solidity: function init(address _addressManager, bytes32 _genesisBlockHash, uint256 _feeBase) returns() +func (_TaikoL1 *TaikoL1Transactor) Init(opts *bind.TransactOpts, _addressManager common.Address, _genesisBlockHash [32]byte, _feeBase *big.Int) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "init", _addressManager, _genesisBlockHash, _feeBase) +} + +// Init is a paid mutator transaction binding the contract method 0x9c5e9f06. +// +// Solidity: function init(address _addressManager, bytes32 _genesisBlockHash, uint256 _feeBase) returns() +func (_TaikoL1 *TaikoL1Session) Init(_addressManager common.Address, _genesisBlockHash [32]byte, _feeBase *big.Int) (*types.Transaction, error) { + return _TaikoL1.Contract.Init(&_TaikoL1.TransactOpts, _addressManager, _genesisBlockHash, _feeBase) +} + +// Init is a paid mutator transaction binding the contract method 0x9c5e9f06. +// +// Solidity: function init(address _addressManager, bytes32 _genesisBlockHash, uint256 _feeBase) returns() +func (_TaikoL1 *TaikoL1TransactorSession) Init(_addressManager common.Address, _genesisBlockHash [32]byte, _feeBase *big.Int) (*types.Transaction, error) { + return _TaikoL1.Contract.Init(&_TaikoL1.TransactOpts, _addressManager, _genesisBlockHash, _feeBase) +} + +// ProposeBlock is a paid mutator transaction binding the contract method 0xa043dbdf. +// +// Solidity: function proposeBlock(bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1Transactor) ProposeBlock(opts *bind.TransactOpts, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "proposeBlock", inputs) +} + +// ProposeBlock is a paid mutator transaction binding the contract method 0xa043dbdf. +// +// Solidity: function proposeBlock(bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1Session) ProposeBlock(inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.ProposeBlock(&_TaikoL1.TransactOpts, inputs) +} + +// ProposeBlock is a paid mutator transaction binding the contract method 0xa043dbdf. +// +// Solidity: function proposeBlock(bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1TransactorSession) ProposeBlock(inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.ProposeBlock(&_TaikoL1.TransactOpts, inputs) +} + +// ProveBlock is a paid mutator transaction binding the contract method 0x8ed7b3be. +// +// Solidity: function proveBlock(uint256 blockId, bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1Transactor) ProveBlock(opts *bind.TransactOpts, blockId *big.Int, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "proveBlock", blockId, inputs) +} + +// ProveBlock is a paid mutator transaction binding the contract method 0x8ed7b3be. +// +// Solidity: function proveBlock(uint256 blockId, bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1Session) ProveBlock(blockId *big.Int, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.ProveBlock(&_TaikoL1.TransactOpts, blockId, inputs) +} + +// ProveBlock is a paid mutator transaction binding the contract method 0x8ed7b3be. +// +// Solidity: function proveBlock(uint256 blockId, bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1TransactorSession) ProveBlock(blockId *big.Int, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.ProveBlock(&_TaikoL1.TransactOpts, blockId, inputs) +} + +// ProveBlockInvalid is a paid mutator transaction binding the contract method 0xa279cec7. +// +// Solidity: function proveBlockInvalid(uint256 blockId, bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1Transactor) ProveBlockInvalid(opts *bind.TransactOpts, blockId *big.Int, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "proveBlockInvalid", blockId, inputs) +} + +// ProveBlockInvalid is a paid mutator transaction binding the contract method 0xa279cec7. +// +// Solidity: function proveBlockInvalid(uint256 blockId, bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1Session) ProveBlockInvalid(blockId *big.Int, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.ProveBlockInvalid(&_TaikoL1.TransactOpts, blockId, inputs) +} + +// ProveBlockInvalid is a paid mutator transaction binding the contract method 0xa279cec7. +// +// Solidity: function proveBlockInvalid(uint256 blockId, bytes[] inputs) returns() +func (_TaikoL1 *TaikoL1TransactorSession) ProveBlockInvalid(blockId *big.Int, inputs [][]byte) (*types.Transaction, error) { + return _TaikoL1.Contract.ProveBlockInvalid(&_TaikoL1.TransactOpts, blockId, inputs) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_TaikoL1 *TaikoL1Transactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_TaikoL1 *TaikoL1Session) RenounceOwnership() (*types.Transaction, error) { + return _TaikoL1.Contract.RenounceOwnership(&_TaikoL1.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_TaikoL1 *TaikoL1TransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _TaikoL1.Contract.RenounceOwnership(&_TaikoL1.TransactOpts) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_TaikoL1 *TaikoL1Transactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_TaikoL1 *TaikoL1Session) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _TaikoL1.Contract.TransferOwnership(&_TaikoL1.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_TaikoL1 *TaikoL1TransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _TaikoL1.Contract.TransferOwnership(&_TaikoL1.TransactOpts, newOwner) +} + +// VerifyBlocks is a paid mutator transaction binding the contract method 0x2fb5ae0a. +// +// Solidity: function verifyBlocks(uint256 maxBlocks) returns() +func (_TaikoL1 *TaikoL1Transactor) VerifyBlocks(opts *bind.TransactOpts, maxBlocks *big.Int) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "verifyBlocks", maxBlocks) +} + +// VerifyBlocks is a paid mutator transaction binding the contract method 0x2fb5ae0a. +// +// Solidity: function verifyBlocks(uint256 maxBlocks) returns() +func (_TaikoL1 *TaikoL1Session) VerifyBlocks(maxBlocks *big.Int) (*types.Transaction, error) { + return _TaikoL1.Contract.VerifyBlocks(&_TaikoL1.TransactOpts, maxBlocks) +} + +// VerifyBlocks is a paid mutator transaction binding the contract method 0x2fb5ae0a. +// +// Solidity: function verifyBlocks(uint256 maxBlocks) returns() +func (_TaikoL1 *TaikoL1TransactorSession) VerifyBlocks(maxBlocks *big.Int) (*types.Transaction, error) { + return _TaikoL1.Contract.VerifyBlocks(&_TaikoL1.TransactOpts, maxBlocks) +} + +// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710. +// +// Solidity: function withdrawBalance() returns() +func (_TaikoL1 *TaikoL1Transactor) WithdrawBalance(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TaikoL1.contract.Transact(opts, "withdrawBalance") +} + +// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710. +// +// Solidity: function withdrawBalance() returns() +func (_TaikoL1 *TaikoL1Session) WithdrawBalance() (*types.Transaction, error) { + return _TaikoL1.Contract.WithdrawBalance(&_TaikoL1.TransactOpts) +} + +// WithdrawBalance is a paid mutator transaction binding the contract method 0x5fd8c710. +// +// Solidity: function withdrawBalance() returns() +func (_TaikoL1 *TaikoL1TransactorSession) WithdrawBalance() (*types.Transaction, error) { + return _TaikoL1.Contract.WithdrawBalance(&_TaikoL1.TransactOpts) +} + +// TaikoL1BlockCommittedIterator is returned from FilterBlockCommitted and is used to iterate over the raw logs and unpacked data for BlockCommitted events raised by the TaikoL1 contract. +type TaikoL1BlockCommittedIterator struct { + Event *TaikoL1BlockCommitted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1BlockCommittedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockCommitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockCommitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1BlockCommittedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1BlockCommittedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1BlockCommitted represents a BlockCommitted event raised by the TaikoL1 contract. +type TaikoL1BlockCommitted struct { + CommitSlot uint64 + CommitHash [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBlockCommitted is a free log retrieval operation binding the contract event 0x51264991e22d808f3bcbb1cbffa82b752eae327c24055259a5c455c0aa5b7136. +// +// Solidity: event BlockCommitted(uint64 commitSlot, bytes32 commitHash) +func (_TaikoL1 *TaikoL1Filterer) FilterBlockCommitted(opts *bind.FilterOpts) (*TaikoL1BlockCommittedIterator, error) { + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "BlockCommitted") + if err != nil { + return nil, err + } + return &TaikoL1BlockCommittedIterator{contract: _TaikoL1.contract, event: "BlockCommitted", logs: logs, sub: sub}, nil +} + +// WatchBlockCommitted is a free log subscription operation binding the contract event 0x51264991e22d808f3bcbb1cbffa82b752eae327c24055259a5c455c0aa5b7136. +// +// Solidity: event BlockCommitted(uint64 commitSlot, bytes32 commitHash) +func (_TaikoL1 *TaikoL1Filterer) WatchBlockCommitted(opts *bind.WatchOpts, sink chan<- *TaikoL1BlockCommitted) (event.Subscription, error) { + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "BlockCommitted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1BlockCommitted) + if err := _TaikoL1.contract.UnpackLog(event, "BlockCommitted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBlockCommitted is a log parse operation binding the contract event 0x51264991e22d808f3bcbb1cbffa82b752eae327c24055259a5c455c0aa5b7136. +// +// Solidity: event BlockCommitted(uint64 commitSlot, bytes32 commitHash) +func (_TaikoL1 *TaikoL1Filterer) ParseBlockCommitted(log types.Log) (*TaikoL1BlockCommitted, error) { + event := new(TaikoL1BlockCommitted) + if err := _TaikoL1.contract.UnpackLog(event, "BlockCommitted", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TaikoL1BlockProposedIterator is returned from FilterBlockProposed and is used to iterate over the raw logs and unpacked data for BlockProposed events raised by the TaikoL1 contract. +type TaikoL1BlockProposedIterator struct { + Event *TaikoL1BlockProposed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1BlockProposedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockProposed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1BlockProposedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1BlockProposedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1BlockProposed represents a BlockProposed event raised by the TaikoL1 contract. +type TaikoL1BlockProposed struct { + Id *big.Int + Meta TaikoDataBlockMetadata + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBlockProposed is a free log retrieval operation binding the contract event 0x344fc5d5f80c4a29bd7e06ad7c28a0c8c9c08d682129da3a31936d5982e4f044. +// +// Solidity: event BlockProposed(uint256 indexed id, (uint256,uint256,bytes32,address,bytes32,bytes32,bytes,uint64,uint64,uint64,uint64) meta) +func (_TaikoL1 *TaikoL1Filterer) FilterBlockProposed(opts *bind.FilterOpts, id []*big.Int) (*TaikoL1BlockProposedIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "BlockProposed", idRule) + if err != nil { + return nil, err + } + return &TaikoL1BlockProposedIterator{contract: _TaikoL1.contract, event: "BlockProposed", logs: logs, sub: sub}, nil +} + +// WatchBlockProposed is a free log subscription operation binding the contract event 0x344fc5d5f80c4a29bd7e06ad7c28a0c8c9c08d682129da3a31936d5982e4f044. +// +// Solidity: event BlockProposed(uint256 indexed id, (uint256,uint256,bytes32,address,bytes32,bytes32,bytes,uint64,uint64,uint64,uint64) meta) +func (_TaikoL1 *TaikoL1Filterer) WatchBlockProposed(opts *bind.WatchOpts, sink chan<- *TaikoL1BlockProposed, id []*big.Int) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "BlockProposed", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1BlockProposed) + if err := _TaikoL1.contract.UnpackLog(event, "BlockProposed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBlockProposed is a log parse operation binding the contract event 0x344fc5d5f80c4a29bd7e06ad7c28a0c8c9c08d682129da3a31936d5982e4f044. +// +// Solidity: event BlockProposed(uint256 indexed id, (uint256,uint256,bytes32,address,bytes32,bytes32,bytes,uint64,uint64,uint64,uint64) meta) +func (_TaikoL1 *TaikoL1Filterer) ParseBlockProposed(log types.Log) (*TaikoL1BlockProposed, error) { + event := new(TaikoL1BlockProposed) + if err := _TaikoL1.contract.UnpackLog(event, "BlockProposed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TaikoL1BlockProvenIterator is returned from FilterBlockProven and is used to iterate over the raw logs and unpacked data for BlockProven events raised by the TaikoL1 contract. +type TaikoL1BlockProvenIterator struct { + Event *TaikoL1BlockProven // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1BlockProvenIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockProven) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockProven) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1BlockProvenIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1BlockProvenIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1BlockProven represents a BlockProven event raised by the TaikoL1 contract. +type TaikoL1BlockProven struct { + Id *big.Int + ParentHash [32]byte + BlockHash [32]byte + Prover common.Address + ProvenAt uint64 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBlockProven is a free log retrieval operation binding the contract event 0x45848a3b2a67571e5876283456675aa3e05880e4f5a73447bd86cef5a181db38. +// +// Solidity: event BlockProven(uint256 indexed id, bytes32 parentHash, bytes32 blockHash, address prover, uint64 provenAt) +func (_TaikoL1 *TaikoL1Filterer) FilterBlockProven(opts *bind.FilterOpts, id []*big.Int) (*TaikoL1BlockProvenIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "BlockProven", idRule) + if err != nil { + return nil, err + } + return &TaikoL1BlockProvenIterator{contract: _TaikoL1.contract, event: "BlockProven", logs: logs, sub: sub}, nil +} + +// WatchBlockProven is a free log subscription operation binding the contract event 0x45848a3b2a67571e5876283456675aa3e05880e4f5a73447bd86cef5a181db38. +// +// Solidity: event BlockProven(uint256 indexed id, bytes32 parentHash, bytes32 blockHash, address prover, uint64 provenAt) +func (_TaikoL1 *TaikoL1Filterer) WatchBlockProven(opts *bind.WatchOpts, sink chan<- *TaikoL1BlockProven, id []*big.Int) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "BlockProven", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1BlockProven) + if err := _TaikoL1.contract.UnpackLog(event, "BlockProven", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBlockProven is a log parse operation binding the contract event 0x45848a3b2a67571e5876283456675aa3e05880e4f5a73447bd86cef5a181db38. +// +// Solidity: event BlockProven(uint256 indexed id, bytes32 parentHash, bytes32 blockHash, address prover, uint64 provenAt) +func (_TaikoL1 *TaikoL1Filterer) ParseBlockProven(log types.Log) (*TaikoL1BlockProven, error) { + event := new(TaikoL1BlockProven) + if err := _TaikoL1.contract.UnpackLog(event, "BlockProven", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TaikoL1BlockVerifiedIterator is returned from FilterBlockVerified and is used to iterate over the raw logs and unpacked data for BlockVerified events raised by the TaikoL1 contract. +type TaikoL1BlockVerifiedIterator struct { + Event *TaikoL1BlockVerified // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1BlockVerifiedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockVerified) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1BlockVerified) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1BlockVerifiedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1BlockVerifiedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1BlockVerified represents a BlockVerified event raised by the TaikoL1 contract. +type TaikoL1BlockVerified struct { + Id *big.Int + BlockHash [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBlockVerified is a free log retrieval operation binding the contract event 0x68b82650828a9621868d09dc161400acbe189fa002e3fb7cf9dea5c2c6f928ee. +// +// Solidity: event BlockVerified(uint256 indexed id, bytes32 blockHash) +func (_TaikoL1 *TaikoL1Filterer) FilterBlockVerified(opts *bind.FilterOpts, id []*big.Int) (*TaikoL1BlockVerifiedIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "BlockVerified", idRule) + if err != nil { + return nil, err + } + return &TaikoL1BlockVerifiedIterator{contract: _TaikoL1.contract, event: "BlockVerified", logs: logs, sub: sub}, nil +} + +// WatchBlockVerified is a free log subscription operation binding the contract event 0x68b82650828a9621868d09dc161400acbe189fa002e3fb7cf9dea5c2c6f928ee. +// +// Solidity: event BlockVerified(uint256 indexed id, bytes32 blockHash) +func (_TaikoL1 *TaikoL1Filterer) WatchBlockVerified(opts *bind.WatchOpts, sink chan<- *TaikoL1BlockVerified, id []*big.Int) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "BlockVerified", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1BlockVerified) + if err := _TaikoL1.contract.UnpackLog(event, "BlockVerified", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBlockVerified is a log parse operation binding the contract event 0x68b82650828a9621868d09dc161400acbe189fa002e3fb7cf9dea5c2c6f928ee. +// +// Solidity: event BlockVerified(uint256 indexed id, bytes32 blockHash) +func (_TaikoL1 *TaikoL1Filterer) ParseBlockVerified(log types.Log) (*TaikoL1BlockVerified, error) { + event := new(TaikoL1BlockVerified) + if err := _TaikoL1.contract.UnpackLog(event, "BlockVerified", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TaikoL1HeaderSyncedIterator is returned from FilterHeaderSynced and is used to iterate over the raw logs and unpacked data for HeaderSynced events raised by the TaikoL1 contract. +type TaikoL1HeaderSyncedIterator struct { + Event *TaikoL1HeaderSynced // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1HeaderSyncedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1HeaderSynced) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1HeaderSynced) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1HeaderSyncedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1HeaderSyncedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1HeaderSynced represents a HeaderSynced event raised by the TaikoL1 contract. +type TaikoL1HeaderSynced struct { + SrcHeight *big.Int + SrcHash [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterHeaderSynced is a free log retrieval operation binding the contract event 0x58313b60ec6c5bfc381e52f0de3ede0faac3cdffea26f7d6bcc3d09b61018691. +// +// Solidity: event HeaderSynced(uint256 indexed srcHeight, bytes32 srcHash) +func (_TaikoL1 *TaikoL1Filterer) FilterHeaderSynced(opts *bind.FilterOpts, srcHeight []*big.Int) (*TaikoL1HeaderSyncedIterator, error) { + + var srcHeightRule []interface{} + for _, srcHeightItem := range srcHeight { + srcHeightRule = append(srcHeightRule, srcHeightItem) + } + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "HeaderSynced", srcHeightRule) + if err != nil { + return nil, err + } + return &TaikoL1HeaderSyncedIterator{contract: _TaikoL1.contract, event: "HeaderSynced", logs: logs, sub: sub}, nil +} + +// WatchHeaderSynced is a free log subscription operation binding the contract event 0x58313b60ec6c5bfc381e52f0de3ede0faac3cdffea26f7d6bcc3d09b61018691. +// +// Solidity: event HeaderSynced(uint256 indexed srcHeight, bytes32 srcHash) +func (_TaikoL1 *TaikoL1Filterer) WatchHeaderSynced(opts *bind.WatchOpts, sink chan<- *TaikoL1HeaderSynced, srcHeight []*big.Int) (event.Subscription, error) { + + var srcHeightRule []interface{} + for _, srcHeightItem := range srcHeight { + srcHeightRule = append(srcHeightRule, srcHeightItem) + } + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "HeaderSynced", srcHeightRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1HeaderSynced) + if err := _TaikoL1.contract.UnpackLog(event, "HeaderSynced", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseHeaderSynced is a log parse operation binding the contract event 0x58313b60ec6c5bfc381e52f0de3ede0faac3cdffea26f7d6bcc3d09b61018691. +// +// Solidity: event HeaderSynced(uint256 indexed srcHeight, bytes32 srcHash) +func (_TaikoL1 *TaikoL1Filterer) ParseHeaderSynced(log types.Log) (*TaikoL1HeaderSynced, error) { + event := new(TaikoL1HeaderSynced) + if err := _TaikoL1.contract.UnpackLog(event, "HeaderSynced", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TaikoL1InitializedIterator is returned from FilterInitialized and is used to iterate over the raw logs and unpacked data for Initialized events raised by the TaikoL1 contract. +type TaikoL1InitializedIterator struct { + Event *TaikoL1Initialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1InitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1Initialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1Initialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1InitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1InitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1Initialized represents a Initialized event raised by the TaikoL1 contract. +type TaikoL1Initialized struct { + Version uint8 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterInitialized is a free log retrieval operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_TaikoL1 *TaikoL1Filterer) FilterInitialized(opts *bind.FilterOpts) (*TaikoL1InitializedIterator, error) { + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return &TaikoL1InitializedIterator{contract: _TaikoL1.contract, event: "Initialized", logs: logs, sub: sub}, nil +} + +// WatchInitialized is a free log subscription operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_TaikoL1 *TaikoL1Filterer) WatchInitialized(opts *bind.WatchOpts, sink chan<- *TaikoL1Initialized) (event.Subscription, error) { + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1Initialized) + if err := _TaikoL1.contract.UnpackLog(event, "Initialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseInitialized is a log parse operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_TaikoL1 *TaikoL1Filterer) ParseInitialized(log types.Log) (*TaikoL1Initialized, error) { + event := new(TaikoL1Initialized) + if err := _TaikoL1.contract.UnpackLog(event, "Initialized", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// TaikoL1OwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the TaikoL1 contract. +type TaikoL1OwnershipTransferredIterator struct { + Event *TaikoL1OwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TaikoL1OwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TaikoL1OwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TaikoL1OwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TaikoL1OwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TaikoL1OwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TaikoL1OwnershipTransferred represents a OwnershipTransferred event raised by the TaikoL1 contract. +type TaikoL1OwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_TaikoL1 *TaikoL1Filterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*TaikoL1OwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _TaikoL1.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &TaikoL1OwnershipTransferredIterator{contract: _TaikoL1.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_TaikoL1 *TaikoL1Filterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *TaikoL1OwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _TaikoL1.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TaikoL1OwnershipTransferred) + if err := _TaikoL1.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_TaikoL1 *TaikoL1Filterer) ParseOwnershipTransferred(log types.Log) (*TaikoL1OwnershipTransferred, error) { + event := new(TaikoL1OwnershipTransferred) + if err := _TaikoL1.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/packages/eventindexer/db.go b/packages/eventindexer/db.go new file mode 100644 index 0000000000..531c2b6f3c --- /dev/null +++ b/packages/eventindexer/db.go @@ -0,0 +1,25 @@ +package eventindexer + +import ( + "database/sql" + + "github.com/cyberhorsey/errors" + "gorm.io/gorm" +) + +var ( + ErrNoDB = errors.Validation.NewWithKeyAndDetail("ERR_NO_DB", "DB is required") +) + +type DBConnectionOpts struct { + Name string + Password string + Host string + Database string + OpenFunc func(dsn string) (DB, error) +} + +type DB interface { + DB() (*sql.DB, error) + GormDB() *gorm.DB +} diff --git a/packages/eventindexer/db/db.go b/packages/eventindexer/db/db.go new file mode 100644 index 0000000000..4f557011cf --- /dev/null +++ b/packages/eventindexer/db/db.go @@ -0,0 +1,25 @@ +package db + +import ( + "database/sql" + + "gorm.io/gorm" +) + +type DB struct { + gormdb *gorm.DB +} + +func (db *DB) DB() (*sql.DB, error) { + return db.gormdb.DB() +} + +func (db *DB) GormDB() *gorm.DB { + return db.gormdb +} + +func New(gormdb *gorm.DB) *DB { + return &DB{ + gormdb: gormdb, + } +} diff --git a/packages/eventindexer/errors.go b/packages/eventindexer/errors.go new file mode 100644 index 0000000000..3d9ff82545 --- /dev/null +++ b/packages/eventindexer/errors.go @@ -0,0 +1,15 @@ +package eventindexer + +import "github.com/cyberhorsey/errors" + +var ( + ErrNoEthClient = errors.Validation.NewWithKeyAndDetail("ERR_NO_ETH_CLIENT", "EthClient is required") + ErrNoEventRepository = errors.Validation.NewWithKeyAndDetail("ERR_NO_EVENT_REPOSITORY", "EventRepository is required") + ErrNoBlockRepository = errors.Validation.NewWithKeyAndDetail( + "ERR_NO_BLOCK_REPOSITORY", + "BlockRepository is required", + ) + ErrNoCORSOrigins = errors.Validation.NewWithKeyAndDetail("ERR_NO_CORS_ORIGINS", "CORS Origins are required") + ErrNoRPCClient = errors.Validation.NewWithKeyAndDetail("ERR_NO_RPC_CLIENT", "RPCClient is required") + ErrInvalidMode = errors.Validation.NewWithKeyAndDetail("ERR_INVALID_MODE", "Mode not supported") +) diff --git a/packages/eventindexer/event.go b/packages/eventindexer/event.go new file mode 100644 index 0000000000..1e0b67bfb6 --- /dev/null +++ b/packages/eventindexer/event.go @@ -0,0 +1,46 @@ +package eventindexer + +import ( + "context" + "math/big" + + "gorm.io/datatypes" +) + +var ( + EventNameBlockProven = "BlockProven" +) + +// Event represents a stored EVM event. The fields will be serialized +// into the Data field to be unmarshalled into a concrete struct +// dependant on the name of the event +type Event struct { + ID int `json:"id"` + Name string `json:"name"` + Data datatypes.JSON `json:"data"` + ChainID int64 `json:"chainID"` + Event string `json:"event"` + Address string `json:"address"` +} + +// SaveEventOpts +type SaveEventOpts struct { + Name string + Data string + ChainID *big.Int + Event string + Address string +} + +type UniqueProversResponse struct { + Address string `json:"address"` + Count int `json:"count"` +} + +// EventRepository is used to interact with events in the store +type EventRepository interface { + Save(ctx context.Context, opts SaveEventOpts) (*Event, error) + FindUniqueProvers( + ctx context.Context, + ) ([]UniqueProversResponse, error) +} diff --git a/packages/eventindexer/flags.go b/packages/eventindexer/flags.go new file mode 100644 index 0000000000..523f62f7dc --- /dev/null +++ b/packages/eventindexer/flags.go @@ -0,0 +1,20 @@ +package eventindexer + +type Mode string + +var ( + SyncMode Mode = "sync" + ResyncMode Mode = "resync" + Modes = []Mode{SyncMode, ResyncMode} +) + +type WatchMode string + +var ( + FilterWatchMode WatchMode = "filter" + SubscribeWatchMode WatchMode = "subscribe" + FilterAndSubscribeWatchMode WatchMode = "filter-and-subscribe" + WatchModes = []WatchMode{FilterWatchMode, SubscribeWatchMode} +) + +type HTTPOnly bool diff --git a/packages/eventindexer/http/errors.go b/packages/eventindexer/http/errors.go new file mode 100644 index 0000000000..438fea9c9d --- /dev/null +++ b/packages/eventindexer/http/errors.go @@ -0,0 +1,14 @@ +package http + +import "github.com/cyberhorsey/errors" + +var ( + ErrNoHTTPFramework = errors.Validation.NewWithKeyAndDetail( + "ERR_NO_HTTP_ENGINE", + "HTTP framework required", + ) + ErrNoRewarder = errors.Validation.NewWithKeyAndDetail( + "ERR_NO_REWARDER", + "Rewarder is required", + ) +) diff --git a/packages/eventindexer/http/get_unique_provers.go b/packages/eventindexer/http/get_unique_provers.go new file mode 100644 index 0000000000..649cb1e2bc --- /dev/null +++ b/packages/eventindexer/http/get_unique_provers.go @@ -0,0 +1,28 @@ +package http + +import ( + "net/http" + + "github.com/cyberhorsey/webutils" + "github.com/labstack/echo/v4" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" +) + +type uniqueProversResp struct { + Provers []eventindexer.UniqueProversResponse `json:"provers"` + UniqueProvers int `json:"uniqueProvers"` +} + +func (srv *Server) GetUniqueProvers(c echo.Context) error { + provers, err := srv.eventRepo.FindUniqueProvers( + c.Request().Context(), + ) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + return c.JSON(http.StatusOK, &uniqueProversResp{ + Provers: provers, + UniqueProvers: len(provers), + }) +} diff --git a/packages/eventindexer/http/routes.go b/packages/eventindexer/http/routes.go new file mode 100644 index 0000000000..7e45ea4112 --- /dev/null +++ b/packages/eventindexer/http/routes.go @@ -0,0 +1,8 @@ +package http + +func (srv *Server) configureRoutes() { + srv.echo.GET("/healthz", srv.Health) + srv.echo.GET("/", srv.Health) + + srv.echo.GET("/uniqueProvers", srv.GetUniqueProvers) +} diff --git a/packages/eventindexer/http/server.go b/packages/eventindexer/http/server.go new file mode 100644 index 0000000000..5234f4dddd --- /dev/null +++ b/packages/eventindexer/http/server.go @@ -0,0 +1,126 @@ +package http + +import ( + "context" + "fmt" + "net/http" + "os" + + "github.com/labstack/echo/v4/middleware" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + + echoprom "github.com/labstack/echo-contrib/prometheus" + echo "github.com/labstack/echo/v4" +) + +type Server struct { + echo *echo.Echo + eventRepo eventindexer.EventRepository +} + +type NewServerOpts struct { + Echo *echo.Echo + EventRepo eventindexer.EventRepository + CorsOrigins []string +} + +func (opts NewServerOpts) Validate() error { + if opts.Echo == nil { + return ErrNoHTTPFramework + } + + if opts.EventRepo == nil { + return eventindexer.ErrNoEventRepository + } + + if opts.CorsOrigins == nil { + return eventindexer.ErrNoCORSOrigins + } + + return nil +} + +func NewServer(opts NewServerOpts) (*Server, error) { + if err := opts.Validate(); err != nil { + return nil, err + } + + srv := &Server{ + echo: opts.Echo, + eventRepo: opts.EventRepo, + } + + corsOrigins := opts.CorsOrigins + if corsOrigins == nil { + corsOrigins = []string{"*"} + } + + srv.configureMiddleware(corsOrigins) + srv.configureRoutes() + + return srv, nil +} + +// Start starts the HTTP server +func (srv *Server) Start(address string) error { + return srv.echo.Start(address) +} + +// Shutdown shuts down the HTTP server +func (srv *Server) Shutdown(ctx context.Context) error { + return srv.echo.Shutdown(ctx) +} + +// ServeHTTP implements the `http.Handler` interface which serves HTTP requests +func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + srv.echo.ServeHTTP(w, r) +} + +// Health endpoints for probes +func (srv *Server) Health(c echo.Context) error { + return c.NoContent(http.StatusOK) +} + +func LogSkipper(c echo.Context) bool { + switch c.Request().URL.Path { + case "/healthz": + return true + case "/metrics": + return true + default: + return false + } +} + +func (srv *Server) configureMiddleware(corsOrigins []string) { + srv.echo.Use(middleware.RequestID()) + + srv.echo.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + Skipper: LogSkipper, + Format: `{"time":"${time_rfc3339_nano}","level":"INFO","message":{"id":"${id}","remote_ip":"${remote_ip}",` + //nolint:lll + `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + //nolint:lll + `"response_status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}",` + + `"bytes_in":${bytes_in},"bytes_out":${bytes_out}}}` + "\n", + Output: os.Stdout, + })) + + srv.echo.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: corsOrigins, + 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/eventindexer/indexer/filter_then_subscribe.go b/packages/eventindexer/indexer/filter_then_subscribe.go new file mode 100644 index 0000000000..85a857278d --- /dev/null +++ b/packages/eventindexer/indexer/filter_then_subscribe.go @@ -0,0 +1,112 @@ +package indexer + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" +) + +func (svc *Service) FilterThenSubscribe( + ctx context.Context, + mode eventindexer.Mode, + watchMode eventindexer.WatchMode, +) error { + chainID, err := svc.ethClient.ChainID(ctx) + if err != nil { + return errors.Wrap(err, "svc.ethClient.ChainID()") + } + + if watchMode == eventindexer.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() { + log.Infof("chain ID %v caught up, subscribing to new incoming events", chainID.Uint64()) + return svc.subscribe(ctx, chainID) + } + + log.Infof("chain ID %v getting events between %v and %v in batches of %v", + chainID.Uint64(), + svc.processingBlockHeight, + header.Number.Int64(), + 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() + } + + filterOpts := &bind.FilterOpts{ + Start: svc.processingBlockHeight, + End: &end, + Context: ctx, + } + + blockProvenEvents, err := svc.taikol1.FilterBlockProven(filterOpts, nil) + if err != nil { + return errors.Wrap(err, "bridge.FilterMessageStatusChanged") + } + + err = svc.saveBlockProvenEvents(ctx, chainID, blockProvenEvents) + if err != nil { + return errors.Wrap(err, "bridge.saveMessageStatusChangedEvents") + } + + header, err := svc.ethClient.HeaderByNumber(ctx, big.NewInt(int64(end))) + if err != nil { + return errors.Wrap(err, "svc.ethClient.HeaderByNumber") + } + + log.Infof("setting last processed block to height: %v, hash: %v", end, header.Hash().Hex()) + + if err := svc.blockRepo.Save(eventindexer.SaveBlockOpts{ + Height: uint64(end), + Hash: header.Hash(), + ChainID: chainID, + }); err != nil { + return errors.Wrap(err, "svc.blockRepo.Save") + } + + eventindexer.BlocksProcessed.Inc() + + svc.processingBlockHeight = uint64(end) + } + + 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, eventindexer.SyncMode, watchMode) + } + + // we are caught up and specified not to subscribe, we can return now + if watchMode == eventindexer.FilterWatchMode { + return nil + } + + return svc.subscribe(ctx, chainID) +} diff --git a/packages/eventindexer/indexer/save_block_proven_event.go b/packages/eventindexer/indexer/save_block_proven_event.go new file mode 100644 index 0000000000..70b58b457e --- /dev/null +++ b/packages/eventindexer/indexer/save_block_proven_event.go @@ -0,0 +1,60 @@ +package indexer + +import ( + "context" + "encoding/json" + "math/big" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/contracts/taikol1" +) + +func (svc *Service) saveBlockProvenEvents( + ctx context.Context, + chainID *big.Int, + events *taikol1.TaikoL1BlockProvenIterator, +) error { + if !events.Next() || events.Event == nil { + log.Infof("no blockProven events") + return nil + } + + for { + event := events.Event + log.Infof("blockProven by: %v", event.Prover.Hex()) + + if err := svc.saveBlockProvenEvent(ctx, chainID, event); err != nil { + return errors.Wrap(err, "svc.saveBlockProvenEvent") + } + + if !events.Next() { + return nil + } + } +} + +func (svc *Service) saveBlockProvenEvent( + ctx context.Context, + chainID *big.Int, + event *taikol1.TaikoL1BlockProven, +) error { + marshaled, err := json.Marshal(event) + if err != nil { + return errors.Wrap(err, "json.Marshal(event)") + } + + _, err = svc.eventRepo.Save(ctx, eventindexer.SaveEventOpts{ + Name: eventindexer.EventNameBlockProven, + Data: string(marshaled), + ChainID: chainID, + Event: eventindexer.EventNameBlockProven, + Address: event.Prover.Hex(), + }) + if err != nil { + return errors.Wrap(err, "svc.eventRepo.Save") + } + + return nil +} diff --git a/packages/eventindexer/indexer/service.go b/packages/eventindexer/indexer/service.go new file mode 100644 index 0000000000..eb9b66c1d1 --- /dev/null +++ b/packages/eventindexer/indexer/service.go @@ -0,0 +1,78 @@ +package indexer + +import ( + "context" + "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/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/contracts/taikol1" +) + +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 eventindexer.EventRepository + blockRepo eventindexer.BlockRepository + ethClient ethClient + + processingBlockHeight uint64 + + blockBatchSize uint64 + subscriptionBackoff time.Duration + + taikol1 *taikol1.TaikoL1 +} + +type NewServiceOpts struct { + EventRepo eventindexer.EventRepository + BlockRepo eventindexer.BlockRepository + EthClient *ethclient.Client + RPCClient *rpc.Client + SrcTaikoAddress common.Address + BlockBatchSize uint64 + SubscriptionBackoff time.Duration +} + +func NewService(opts NewServiceOpts) (*Service, error) { + if opts.EventRepo == nil { + return nil, eventindexer.ErrNoEventRepository + } + + if opts.EthClient == nil { + return nil, eventindexer.ErrNoEthClient + } + + if opts.RPCClient == nil { + return nil, eventindexer.ErrNoRPCClient + } + + taikoL1, err := taikol1.NewTaikoL1(opts.SrcTaikoAddress, opts.EthClient) + if err != nil { + return nil, errors.Wrap(err, "contracts.NewTaikoL1") + } + + return &Service{ + eventRepo: opts.EventRepo, + blockRepo: opts.BlockRepo, + ethClient: opts.EthClient, + taikol1: taikoL1, + + blockBatchSize: opts.BlockBatchSize, + subscriptionBackoff: opts.SubscriptionBackoff, + }, nil +} diff --git a/packages/eventindexer/indexer/set_initial_processing_block_height.go b/packages/eventindexer/indexer/set_initial_processing_block_height.go new file mode 100644 index 0000000000..9a8fda9451 --- /dev/null +++ b/packages/eventindexer/indexer/set_initial_processing_block_height.go @@ -0,0 +1,45 @@ +package indexer + +import ( + "context" + "math/big" + + "github.com/pkg/errors" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" +) + +func (svc *Service) setInitialProcessingBlockByMode( + ctx context.Context, + mode eventindexer.Mode, + chainID *big.Int, +) error { + stateVars, err := svc.taikol1.GetStateVariables(nil) + if err != nil { + return errors.Wrap(err, "svc.taikoL1.GetStateVariables") + } + + startingBlock := stateVars.GenesisHeight + + switch mode { + case eventindexer.SyncMode: + latestProcessedBlock, err := svc.blockRepo.GetLatestBlockProcessed( + chainID, + ) + if err != nil { + return errors.Wrap(err, "svc.blockRepo.GetLatestBlock()") + } + + if latestProcessedBlock.Height != 0 { + startingBlock = latestProcessedBlock.Height + } + + svc.processingBlockHeight = startingBlock + + return nil + case eventindexer.ResyncMode: + svc.processingBlockHeight = startingBlock + return nil + default: + return eventindexer.ErrInvalidMode + } +} diff --git a/packages/eventindexer/indexer/subscribe.go b/packages/eventindexer/indexer/subscribe.go new file mode 100644 index 0000000000..e000c70310 --- /dev/null +++ b/packages/eventindexer/indexer/subscribe.go @@ -0,0 +1,86 @@ +package indexer + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/event" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "github.com/taikoxyz/taiko-mono/packages/eventindexer/contracts/taikol1" +) + +// subscribe subscribes to latest events +func (svc *Service) subscribe(ctx context.Context, chainID *big.Int) error { + log.Info("subscribing to new events") + + errChan := make(chan error) + + go svc.subscribeBlockProven(ctx, chainID, errChan) + + // nolint: gosimple + for { + select { + case <-ctx.Done(): + log.Info("context finished") + return nil + case err := <-errChan: + eventindexer.ErrorsEncounteredDuringSubscription.Inc() + + return errors.Wrap(err, "errChan") + } + } +} + +func (svc *Service) subscribeBlockProven(ctx context.Context, chainID *big.Int, errChan chan error) { + sink := make(chan *taikol1.TaikoL1BlockProven) + + sub := event.ResubscribeErr(svc.subscriptionBackoff, func(ctx context.Context, err error) (event.Subscription, error) { + if err != nil { + log.Errorf("svc.bridge.WatchBlockProven: %v", err) + } + log.Info("resubscribing to BlockProven events") + + return svc.taikol1.WatchBlockProven(&bind.WatchOpts{ + Context: ctx, + }, sink, nil) + }) + + defer sub.Unsubscribe() + + for { + select { + case <-ctx.Done(): + log.Info("context finished") + return + case err := <-sub.Err(): + errChan <- errors.Wrap(err, "sub.Err()") + case event := <-sink: + log.Infof("blockProvenEvent from subscription for prover %v", event.Prover.Hex()) + + if err := svc.saveBlockProvenEvent(ctx, chainID, event); err != nil { + log.Errorf("svc.subscribe, svc.saveBlockProvenEvent: %v", err) + } + + block, err := svc.blockRepo.GetLatestBlockProcessed(chainID) + if err != nil { + log.Errorf("svc.subscribe, blockRepo.GetLatestBlockProcessed: %v", err) + } + + if block.Height < event.Raw.BlockNumber { + err = svc.blockRepo.Save(eventindexer.SaveBlockOpts{ + Height: event.Raw.BlockNumber, + Hash: event.Raw.BlockHash, + ChainID: chainID, + }) + if err != nil { + log.Errorf("svc.subscribe, svc.blockRepo.Save: %v", err) + } + + eventindexer.BlocksProcessed.Inc() + } + } + } +} diff --git a/packages/eventindexer/migrations/1666650599_create_events_table.sql b/packages/eventindexer/migrations/1666650599_create_events_table.sql new file mode 100644 index 0000000000..324204d675 --- /dev/null +++ b/packages/eventindexer/migrations/1666650599_create_events_table.sql @@ -0,0 +1,18 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS events ( + id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + event VARCHAR(255) NOT NULL DEFAULT "", + chain_id int NOT NULL, + data JSON NOT NULL, + address VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- +goose StatementEnd +-- +goose Down +-- +goose StatementBegin +DROP TABLE events; +-- +goose StatementEnd diff --git a/packages/eventindexer/migrations/1666650700_create_processed_blocks_table.sql b/packages/eventindexer/migrations/1666650700_create_processed_blocks_table.sql new file mode 100644 index 0000000000..4c8726067d --- /dev/null +++ b/packages/eventindexer/migrations/1666650700_create_processed_blocks_table.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS processed_blocks ( + id int NOT NULL PRIMARY KEY AUTO_INCREMENT, + block_height int NOT NULL, + hash VARCHAR(255) NOT NULL UNIQUE, + chain_id int NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- +goose StatementEnd +-- +goose Down +-- +goose StatementBegin +DROP TABLE processed_blocks; +-- +goose StatementEnd + diff --git a/packages/eventindexer/package.json b/packages/eventindexer/package.json new file mode 100644 index 0000000000..971bea3a62 --- /dev/null +++ b/packages/eventindexer/package.json @@ -0,0 +1,5 @@ +{ + "name": "@taiko/relayer", + "version": "0.3.0", + "private": true +} diff --git a/packages/eventindexer/prometheus.go b/packages/eventindexer/prometheus.go new file mode 100644 index 0000000000..ce7e632ea6 --- /dev/null +++ b/packages/eventindexer/prometheus.go @@ -0,0 +1,29 @@ +package eventindexer + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + EventsProcessed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "events_processed_ops_total", + Help: "The total number of processed events", + }) + BlocksProcessed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "blocks_processed_ops_total", + Help: "The total number of processed blocks", + }) + BlocksScanned = promauto.NewCounter(prometheus.CounterOpts{ + Name: "blocks_scanned_ops_total", + Help: "The total number of scanned blocks. Acts as heartbeat metric.", + }) + BlocksScannedError = promauto.NewCounter(prometheus.CounterOpts{ + Name: "blocks_scanned_error_ops_total", + Help: "The total number of scanned block errors.", + }) + ErrorsEncounteredDuringSubscription = promauto.NewCounter(prometheus.CounterOpts{ + Name: "errors_encountered_during_subscription_opts_total", + Help: "The total number of errors that occured during active subscription", + }) +) diff --git a/packages/eventindexer/repo/block.go b/packages/eventindexer/repo/block.go new file mode 100644 index 0000000000..159700dd60 --- /dev/null +++ b/packages/eventindexer/repo/block.go @@ -0,0 +1,62 @@ +package repo + +import ( + "math/big" + + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "gorm.io/gorm" +) + +type BlockRepository struct { + db eventindexer.DB +} + +func NewBlockRepository(db eventindexer.DB) (*BlockRepository, error) { + if db == nil { + return nil, eventindexer.ErrNoDB + } + + return &BlockRepository{ + db: db, + }, nil +} + +func (r *BlockRepository) startQuery() *gorm.DB { + return r.db.GormDB().Table("processed_blocks") +} + +func (r *BlockRepository) Save(opts eventindexer.SaveBlockOpts) error { + exists := &eventindexer.Block{} + _ = r.startQuery().Where("block_height = ?", opts.Height).Where("chain_id = ?", opts.ChainID.Int64()).First(exists) + // block procesed already + if exists.Height == opts.Height { + return nil + } + + b := &eventindexer.Block{ + Height: opts.Height, + Hash: opts.Hash.String(), + ChainID: opts.ChainID.Int64(), + } + if err := r.startQuery().Create(b).Error; err != nil { + return err + } + + return nil +} + +func (r *BlockRepository) GetLatestBlockProcessed(chainID *big.Int) (*eventindexer.Block, error) { + b := &eventindexer.Block{} + if err := r. + startQuery(). + Raw(`SELECT id, block_height, hash, chain_id + FROM processed_blocks + WHERE block_height = + ( SELECT MAX(block_height) from processed_blocks + WHERE chain_id = ? )`, chainID.Int64()). + FirstOrInit(b).Error; err != nil { + return nil, err + } + + return b, nil +} diff --git a/packages/eventindexer/repo/event.go b/packages/eventindexer/repo/event.go new file mode 100644 index 0000000000..c2a93c1457 --- /dev/null +++ b/packages/eventindexer/repo/event.go @@ -0,0 +1,53 @@ +package repo + +import ( + "context" + + "github.com/pkg/errors" + "github.com/taikoxyz/taiko-mono/packages/eventindexer" + "gorm.io/datatypes" +) + +type EventRepository struct { + db eventindexer.DB +} + +func NewEventRepository(db eventindexer.DB) (*EventRepository, error) { + if db == nil { + return nil, eventindexer.ErrNoDB + } + + return &EventRepository{ + db: db, + }, nil +} + +func (r *EventRepository) Save(ctx context.Context, opts eventindexer.SaveEventOpts) (*eventindexer.Event, error) { + e := &eventindexer.Event{ + Data: datatypes.JSON(opts.Data), + ChainID: opts.ChainID.Int64(), + Name: opts.Name, + Event: opts.Event, + Address: opts.Address, + } + + if err := r.db.GormDB().Create(e).Error; err != nil { + return nil, errors.Wrap(err, "r.db.Create") + } + + return e, nil +} + +func (r *EventRepository) FindUniqueProvers( + ctx context.Context, +) ([]eventindexer.UniqueProversResponse, error) { + addrs := make([]eventindexer.UniqueProversResponse, 0) + + if err := r.db.GormDB(). + Raw("SELECT address, count(*) AS count FROM events GROUP BY address"). + FirstOrInit(&addrs).Error; err != nil { + return nil, errors.Wrap(err, "r.db.FirstOrInit") + } + + return addrs, nil +} From 813dfd54fb73a213f06aac7e15ff4530ec632e11 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 23 Mar 2023 14:01:28 -0700 Subject: [PATCH 02/20] add status page indicator --- packages/status-page/.default.env | 3 ++- packages/status-page/src/App.svelte | 1 + .../status-page/src/pages/home/Home.svelte | 18 ++++++++++++++++++ .../status-page/src/utils/getNumProvers.ts | 18 ++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 packages/status-page/src/utils/getNumProvers.ts diff --git a/packages/status-page/.default.env b/packages/status-page/.default.env index 1facacb4ff..871254df35 100644 --- a/packages/status-page/.default.env +++ b/packages/status-page/.default.env @@ -6,4 +6,5 @@ VITE_TAIKO_L1_ADDRESS="0x7B3AF414448ba906f02a1CA307C56c4ADFF27ce7" VITE_L1_EXPLORER_URL="https://l1explorer.a1.taiko.xyz" VITE_L2_EXPLORER_URL="https://l2explorer.a1.taiko.xyz" VITE_FEE_TOKEN_SYMBOL="TKO"; -VITE_ORACLE_PROVER_ADDRESS="0x1567CDAb5F7a69154e61A16D8Ff5eE6A3e991b39" \ No newline at end of file +VITE_ORACLE_PROVER_ADDRESS="0x1567CDAb5F7a69154e61A16D8Ff5eE6A3e991b39" +VITE_EVENT_INDEXER_API_URL="http://localhost:4100" \ No newline at end of file diff --git a/packages/status-page/src/App.svelte b/packages/status-page/src/App.svelte index ae84186310..0bba71fdce 100644 --- a/packages/status-page/src/App.svelte +++ b/packages/status-page/src/App.svelte @@ -29,6 +29,7 @@ oracleProverAddress: import.meta.env.ORACLE_PROVER_ADDRESS || "0x1567CDAb5F7a69154e61A16D8Ff5eE6A3e991b39", + eventIndexerApiUrl: import.meta.env.VITE_EVENT_INDEXER_API_URL, }, userData: {}, }), diff --git a/packages/status-page/src/pages/home/Home.svelte b/packages/status-page/src/pages/home/Home.svelte index 8139939415..788d44d536 100644 --- a/packages/status-page/src/pages/home/Home.svelte +++ b/packages/status-page/src/pages/home/Home.svelte @@ -17,6 +17,7 @@ import { getStateVariables } from "../../utils/getStateVariables"; import { truncateString } from "../../utils/truncateString"; import TaikoL1 from "../../constants/abi/TaikoL1"; + import { getNumProvers } from "../../utils/getNumProvers"; export let l1Provider: ethers.providers.JsonRpcProvider; export let l1TaikoAddress: string; @@ -26,8 +27,25 @@ export let l2ExplorerUrl: string; export let feeTokenSymbol: string; export let oracleProverAddress: string; + export let eventIndexerApiUrl: string; let statusIndicators: StatusIndicatorProp[] = [ + { + statusFunc: async ( + provider: ethers.providers.JsonRpcProvider, + address: string + ) => (await getNumProvers(eventIndexerApiUrl)).uniqueProvers, + watchStatusFunc: watchHeaderSynced, + provider: l1Provider, + contractAddress: l1TaikoAddress, + header: "Unique Provers", + intervalInMs: 0, + colorFunc: (value: Status) => { + return "green"; + }, + tooltip: + "The numbe of unique provers who successfully submitted a proof to the TaikoL1 smart contract.", + }, { statusFunc: getLatestSyncedHeader, watchStatusFunc: watchHeaderSynced, diff --git a/packages/status-page/src/utils/getNumProvers.ts b/packages/status-page/src/utils/getNumProvers.ts new file mode 100644 index 0000000000..2d220bda51 --- /dev/null +++ b/packages/status-page/src/utils/getNumProvers.ts @@ -0,0 +1,18 @@ +import axios from "axios"; + +export type UniqueProver = { + address: string; + count: number; +}; +export type UniqueProverResponse = { + uniqueProvers: number; + provers: UniqueProver[]; +}; +export const getNumProvers = async ( + eventIndexerApiUrl: string +): Promise => { + const resp = await axios.get( + `${eventIndexerApiUrl}/uniqueProvers` + ); + return resp.data; +}; From cacc9668cb5acb200cd0bc31253e8d5ca13f0c77 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 23 Mar 2023 14:03:16 -0700 Subject: [PATCH 03/20] home --- packages/status-page/src/pages/home/Home.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/status-page/src/pages/home/Home.svelte b/packages/status-page/src/pages/home/Home.svelte index 788d44d536..b74628c7d5 100644 --- a/packages/status-page/src/pages/home/Home.svelte +++ b/packages/status-page/src/pages/home/Home.svelte @@ -35,7 +35,6 @@ provider: ethers.providers.JsonRpcProvider, address: string ) => (await getNumProvers(eventIndexerApiUrl)).uniqueProvers, - watchStatusFunc: watchHeaderSynced, provider: l1Provider, contractAddress: l1TaikoAddress, header: "Unique Provers", From 02c147aa02c003bbc8359ff3f76c1f9068d2e67f Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 23 Mar 2023 14:22:01 -0700 Subject: [PATCH 04/20] pr title + readme --- .github/workflows/lint-pr.yml | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml index 4cda3bf157..e50cb5c99f 100644 --- a/.github/workflows/lint-pr.yml +++ b/.github/workflows/lint-pr.yml @@ -20,6 +20,7 @@ jobs: repo branding bridge-ui + eventindexer protocol relayer status-page diff --git a/README.md b/README.md index 4424a477c0..3a517dbe49 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ taiko-mono ├── packages │ ├── branding: Taiko branding materials │ ├── bridge-ui: Taiko Bridge frontend UI +│ ├── eventindexer: Event indexer │ ├── protocol: Taiko ZK-Rollup L2 Protocol and Taiko Bridge smart contracts │ ├── relayer: Bridge relayer (backend) │ ├── status-page: Taiko Protocol status page From 199ff00efdf7492ca0652564d66ca6e890aeb25b Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 23 Mar 2023 14:27:18 -0700 Subject: [PATCH 05/20] rm depth 1 --- packages/eventindexer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eventindexer/Dockerfile b/packages/eventindexer/Dockerfile index c9528c3ff5..087bb427fb 100644 --- a/packages/eventindexer/Dockerfile +++ b/packages/eventindexer/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.19.3 as builder RUN apt install git curl -RUN git clone --depth 1 https://github.com/taikoxyz/taiko-mono /taiko-mono +RUN git clone https://github.com/taikoxyz/taiko-mono /taiko-mono WORKDIR /taiko-mono/packages/eventindexer From 65f98758807e71d625d08c5bfd1464f9498e3b30 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Thu, 23 Mar 2023 15:42:59 -0700 Subject: [PATCH 06/20] add details model --- packages/eventindexer/Dockerfile | 2 +- packages/eventindexer/prometheus.go | 4 ---- .../src/components/DetailsModal.svelte | 17 +++++++++++++ .../src/components/StatusIndicator.svelte | 3 +++ .../status-page/src/pages/home/Home.svelte | 24 ++++++++++++++++++- .../src/utils/addressSubsection.ts | 4 ++++ 6 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 packages/status-page/src/components/DetailsModal.svelte create mode 100644 packages/status-page/src/utils/addressSubsection.ts diff --git a/packages/eventindexer/Dockerfile b/packages/eventindexer/Dockerfile index 087bb427fb..c9528c3ff5 100644 --- a/packages/eventindexer/Dockerfile +++ b/packages/eventindexer/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.19.3 as builder RUN apt install git curl -RUN git clone https://github.com/taikoxyz/taiko-mono /taiko-mono +RUN git clone --depth 1 https://github.com/taikoxyz/taiko-mono /taiko-mono WORKDIR /taiko-mono/packages/eventindexer diff --git a/packages/eventindexer/prometheus.go b/packages/eventindexer/prometheus.go index ce7e632ea6..b12557a2fc 100644 --- a/packages/eventindexer/prometheus.go +++ b/packages/eventindexer/prometheus.go @@ -14,10 +14,6 @@ var ( Name: "blocks_processed_ops_total", Help: "The total number of processed blocks", }) - BlocksScanned = promauto.NewCounter(prometheus.CounterOpts{ - Name: "blocks_scanned_ops_total", - Help: "The total number of scanned blocks. Acts as heartbeat metric.", - }) BlocksScannedError = promauto.NewCounter(prometheus.CounterOpts{ Name: "blocks_scanned_error_ops_total", Help: "The total number of scanned block errors.", diff --git a/packages/status-page/src/components/DetailsModal.svelte b/packages/status-page/src/components/DetailsModal.svelte new file mode 100644 index 0000000000..a7ea557e7f --- /dev/null +++ b/packages/status-page/src/components/DetailsModal.svelte @@ -0,0 +1,17 @@ + + + +
+
+ +
+ +
+
diff --git a/packages/status-page/src/components/StatusIndicator.svelte b/packages/status-page/src/components/StatusIndicator.svelte index f6c2393d44..e086115118 100644 --- a/packages/status-page/src/components/StatusIndicator.svelte +++ b/packages/status-page/src/components/StatusIndicator.svelte @@ -7,6 +7,7 @@ import { fade } from "svelte/transition"; import Tooltip from "./Tooltip.svelte"; import TooltipModal from "./TooltipModal.svelte"; + import DetailsModal from "./DetailsModal.svelte"; export let provider: ethers.providers.JsonRpcProvider; export let contractAddress: string; @@ -40,6 +41,8 @@ let tooltipOpen: boolean = false; + let detailsOpen: boolean = false; + onMount(async () => { try { if (status) { diff --git a/packages/status-page/src/pages/home/Home.svelte b/packages/status-page/src/pages/home/Home.svelte index b74628c7d5..a26388a031 100644 --- a/packages/status-page/src/pages/home/Home.svelte +++ b/packages/status-page/src/pages/home/Home.svelte @@ -1,5 +1,5 @@