Commit 96c4f919 authored by Matthew Slipper's avatar Matthew Slipper

Merge remote-tracking branch 'upstream/develop' into proxyd-max-block-range

parents 0378f716 bcfbee52
---
'@eth-optimism/sdk': patch
---
Updated npm dependencies to latest
---
'@eth-optimism/core-utils': patch
---
Upgraded npm dependencies to latest
---
'@eth-optimism/chain-mon': patch
---
Upgraded npm dependencies to latest
---
'@eth-optimism/common-ts': patch
---
Updated npm dependencies of common-ts
...@@ -375,6 +375,10 @@ jobs: ...@@ -375,6 +375,10 @@ jobs:
- pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }} - pnpm-packages-v2-{{ checksum "pnpm-lock.yaml" }}
- check-changed: - check-changed:
patterns: contracts-bedrock,op-node patterns: contracts-bedrock,op-node
# populate node modules from the cache
- run:
name: Install dependencies
command: pnpm install --frozen-lockfile --prefer-offline
- run: - run:
name: build contracts name: build contracts
command: pnpm build command: pnpm build
...@@ -393,13 +397,6 @@ jobs: ...@@ -393,13 +397,6 @@ jobs:
FOUNDRY_PROFILE: ci FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
no_output_timeout: 15m no_output_timeout: 15m
- run:
name: validate deploy configs
command: |
pnpm validate-deploy-configs || echo "export DEPLOY_CONFIG_STATUS=1" >> "$BASH_ENV"
environment:
FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock
- run: - run:
name: storage snapshot name: storage snapshot
command: | command: |
...@@ -418,6 +415,10 @@ jobs: ...@@ -418,6 +415,10 @@ jobs:
pnpm autogen:invariant-docs pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV" git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run:
name: check deploy configs || echo "export DEPLOY_CONFIGS_STATUS=1" >> "$BASH_ENV"
command: pnpm validate-deploy-configs
working_directory: packages/contracts-bedrock
- run: - run:
name: check statuses name: check statuses
command: | command: |
...@@ -429,20 +430,20 @@ jobs: ...@@ -429,20 +430,20 @@ jobs:
FAILED=1 FAILED=1
echo "Gas snapshot failed, see job output for details." echo "Gas snapshot failed, see job output for details."
fi fi
if [[ "$DEPLOY_CONFIG_STATUS" -ne 0 ]]; then
FAILED=1
echo "Deploy configs invalid, see job output for details."
fi
if [[ "$STORAGE_SNAPSHOT_STATUS" -ne 0 ]]; then if [[ "$STORAGE_SNAPSHOT_STATUS" -ne 0 ]]; then
echo "Storage snapshot failed, see job output for details." echo "Storage snapshot failed, see job output for details."
FAILED=1 FAILED=1
fi fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details."
FAILED=1
fi
if [[ "$INVARIANT_DOCS_STATUS" -ne 0 ]]; then if [[ "$INVARIANT_DOCS_STATUS" -ne 0 ]]; then
echo "Invariant docs failed, see job output for details." echo "Invariant docs failed, see job output for details."
FAILED=1 FAILED=1
fi fi
if [[ "$SEMVER_LOCK_STATUS" -ne 0 ]]; then if [[ "$DEPLOY_CONFIGS_STATUS" -ne 0 ]]; then
echo "Semver lock failed, see job output for details." echo "Deploy config check failed, see job output for details."
FAILED=1 FAILED=1
fi fi
if [[ "$FAILED" -ne 0 ]]; then if [[ "$FAILED" -ne 0 ]]; then
...@@ -741,8 +742,7 @@ jobs: ...@@ -741,8 +742,7 @@ jobs:
- checkout - checkout
- run: - run:
name: run lint name: run lint
command: | command: make lint
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" ./...
working_directory: <<parameters.module>> working_directory: <<parameters.module>>
go-test: go-test:
...@@ -778,6 +778,9 @@ jobs: ...@@ -778,6 +778,9 @@ jobs:
use_http: use_http:
description: If the op-e2e package should use HTTP clients description: If the op-e2e package should use HTTP clients
type: string type: string
use_external:
description: The extra-process shim (if any) that should be used
type: string
docker: docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
resource_class: xlarge resource_class: xlarge
...@@ -788,6 +791,13 @@ jobs: ...@@ -788,6 +791,13 @@ jobs:
- run: - run:
name: prep results dir name: prep results dir
command: mkdir -p /tmp/test-results command: mkdir -p /tmp/test-results
- when:
condition: <<parameters.use_external>>
steps:
- run:
name: Build Shim
command: make -C <<parameters.use_external>>
working_directory: <<parameters.module>>
- run: - run:
name: install geth name: install geth
command: make install-geth command: make install-geth
...@@ -807,9 +817,11 @@ jobs: ...@@ -807,9 +817,11 @@ jobs:
# Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional # Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional
# constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building. # constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building.
# Note: -parallel must be set to match the number of cores in the resource class # Note: -parallel must be set to match the number of cores in the resource class
export TEST_SUFFIX="<<parameters.use_external>>"
export EXTERNAL_L2="$(test -z '<<parameters.use_external>>' || echo '<<parameters.use_external>>/shim')"
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \ OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
--format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \ --format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>$TEST_SUFFIX.xml \
-- -timeout=20m -parallel=8 ./... -- -timeout=20m -parallel=8 --externalL2 "$EXTERNAL_L2" ./...
working_directory: <<parameters.module>> working_directory: <<parameters.module>>
- store_test_results: - store_test_results:
path: /tmp/test-results path: /tmp/test-results
...@@ -838,7 +850,7 @@ jobs: ...@@ -838,7 +850,7 @@ jobs:
patterns: <<parameters.working_directory>>,<<parameters.dependencies>> patterns: <<parameters.working_directory>>,<<parameters.dependencies>>
- run: - run:
name: Lint name: Lint
command: golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 2m -e "errors.As" -e "errors.Is" ./... command: make lint
working_directory: <<parameters.working_directory>> working_directory: <<parameters.working_directory>>
- store_test_results: - store_test_results:
path: /test-results path: /test-results
...@@ -888,7 +900,7 @@ jobs: ...@@ -888,7 +900,7 @@ jobs:
name: Test name: Test
command: | command: |
mkdir -p /test-results mkdir -p /test-results
DB_USER=postgres gotestsum --junitfile /test-results/tests.xml -- -parallel=4 ./... DB_USER=postgres gotestsum --format=standard-verbose --junitfile /test-results/tests.xml -- -parallel=4 ./...
working_directory: indexer working_directory: indexer
- run: - run:
name: Build name: Build
...@@ -953,17 +965,8 @@ jobs: ...@@ -953,17 +965,8 @@ jobs:
name: Bring up the stack name: Bring up the stack
command: make devnet-up command: make devnet-up
- run: - run:
name: Check L2 config name: Test the stack
command: go run cmd/check-l2/main.go --l2-rpc-url http://localhost:9545 --l1-rpc-url http://localhost:8545 command: make devnet-test
working_directory: op-chain-ops
- run:
name: Deposit ERC20 through the bridge
command: timeout 8m npx hardhat deposit-erc20 --network devnetL1 --l1-contracts-json-path ../../.devnet/addresses.json
working_directory: packages/sdk
- run:
name: Deposit ETH through the bridge
command: timeout 8m npx hardhat deposit-eth --network devnetL1 --l1-contracts-json-path ../../.devnet/addresses.json
working_directory: packages/sdk
- run: - run:
name: Dump op-node logs name: Dump op-node logs
command: | command: |
...@@ -1283,10 +1286,17 @@ workflows: ...@@ -1283,10 +1286,17 @@ workflows:
name: op-e2e-WS-tests name: op-e2e-WS-tests
module: op-e2e module: op-e2e
use_http: "false" use_http: "false"
use_external: ""
- go-e2e-test: - go-e2e-test:
name: op-e2e-HTTP-tests name: op-e2e-HTTP-tests
module: op-e2e module: op-e2e
use_http: "true" use_http: "true"
use_external: ""
- go-e2e-test:
name: op-e2e-WS-tests-external-geth
module: op-e2e
use_http: "false"
use_external: "external_geth"
- bedrock-go-tests: - bedrock-go-tests:
requires: requires:
- op-batcher-lint - op-batcher-lint
......
...@@ -6,7 +6,7 @@ updates: ...@@ -6,7 +6,7 @@ updates:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels: labels:
- dependabot - M-dependabot
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: "/" directory: "/"
...@@ -14,7 +14,7 @@ updates: ...@@ -14,7 +14,7 @@ updates:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels: labels:
- dependabot - M-dependabot
- package-ecosystem: npm - package-ecosystem: npm
directory: "/" directory: "/"
...@@ -25,7 +25,7 @@ updates: ...@@ -25,7 +25,7 @@ updates:
open-pull-requests-limit: 10 open-pull-requests-limit: 10
versioning-strategy: auto versioning-strategy: auto
labels: labels:
- dependabot - M-dependabot
- package-ecosystem: gomod - package-ecosystem: gomod
directory: "/" directory: "/"
...@@ -33,4 +33,4 @@ updates: ...@@ -33,4 +33,4 @@ updates:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels: labels:
- dependabot - M-dependabot
...@@ -12,6 +12,9 @@ pull_request_rules: ...@@ -12,6 +12,9 @@ pull_request_rules:
- "label!=do-not-merge" - "label!=do-not-merge"
- "label!=multiple-reviewers" - "label!=multiple-reviewers"
- "label!=mergify-ignore" - "label!=mergify-ignore"
- "label!=M-do-not-merge"
- "label!=M-multiple-reviewers"
- "label!=M-mergify-ignore"
- "base=develop" - "base=develop"
actions: actions:
queue: queue:
...@@ -27,14 +30,14 @@ pull_request_rules: ...@@ -27,14 +30,14 @@ pull_request_rules:
This PR has been added to the merge queue, and will be merged soon. This PR has been added to the merge queue, and will be merged soon.
label: label:
add: add:
- on-merge-train - S-on-merge-train
- name: Remove merge train label - name: Remove merge train label
conditions: conditions:
- "queue-position = -1" - "queue-position = -1"
actions: actions:
label: label:
remove: remove:
- on-merge-train - S-on-merge-train
- name: Ask to resolve conflict - name: Ask to resolve conflict
conditions: conditions:
- conflict - conflict
...@@ -43,14 +46,14 @@ pull_request_rules: ...@@ -43,14 +46,14 @@ pull_request_rules:
message: Hey @{{author}}! This PR has merge conflicts. Please fix them before continuing review. message: Hey @{{author}}! This PR has merge conflicts. Please fix them before continuing review.
label: label:
add: add:
- conflict - S-conflict
- name: Remove conflicts label when conflicts gone - name: Remove conflicts label when conflicts gone
conditions: conditions:
- -conflict - -conflict
actions: actions:
label: label:
remove: remove:
- conflict - S-conflict
- name: Notify author when added to merge queue - name: Notify author when added to merge queue
conditions: conditions:
- "check-pending=Queue: Embarked in merge train" - "check-pending=Queue: Embarked in merge train"
...@@ -71,33 +74,250 @@ pull_request_rules: ...@@ -71,33 +74,250 @@ pull_request_rules:
More details can be found on the `Queue: Embarked in merge train` More details can be found on the `Queue: Embarked in merge train`
check-run. check-run.
- name: Add indexer tag and ecopod reviewers - name: Add A-cannon label
conditions:
- 'files~=^cannon/'
actions:
label:
add:
- A-cannon
- name: Add A-indexer label and ecopod reviewers
conditions: conditions:
- 'files~=^indexer/' - 'files~=^indexer/'
- '#label<5'
actions: actions:
label: label:
add: add:
- indexer - A-indexer
request_reviews: request_reviews:
users: users:
- roninjin10 - roninjin10
- name: Add sdk tag and ecopod reviewers - name: Add A-op-batcher label
conditions: conditions:
- 'files~=^packages/sdk/' - 'files~=^op-batcher/'
actions:
label:
add:
- A-op-batcher
- name: Add A-op-bindings label
conditions:
- 'files~=^op-bindings/'
actions:
label:
add:
- A-op-bindings
- name: Add A-op-bootnode label
conditions:
- 'files~=^op-bootnode/'
- '#label<5'
actions:
label:
add:
- A-op-bootnode
- name: Add A-op-chain-ops label
conditions:
- 'files~=^op-chain-ops/'
actions:
label:
add:
- A-op-chain-ops
- name: Add A-op-challenger label
conditions:
- 'files~=^op-challenger/'
actions:
label:
add:
- A-op-challenger
- name: Add A-op-e2e label
conditions:
- 'files~=^op-e2e/'
- '#label<5'
actions:
label:
add:
- A-op-e2e
- name: Add A-op-exporter label
conditions:
- 'files~=^op-exporter/'
- '#label<5'
actions:
label:
add:
- A-op-exporter
- name: Add A-op-heartbeat label
conditions:
- 'files~=^op-heartbeat/'
- '#label<5'
actions:
label:
add:
- A-op-heartbeat
- name: Add A-op-node label
conditions:
- 'files~=^op-node/'
actions:
label:
add:
- A-op-node
- name: Add A-op-preimage label
conditions:
- 'files~=^op-preimage/'
- '#label<5'
actions:
label:
add:
- A-op-preimage
- name: Add A-op-program label
conditions:
- 'files~=^op-program/'
actions:
label:
add:
- A-op-program
- name: Add A-op-proposer label
conditions:
- 'files~=^op-proposer/'
actions:
label:
add:
- A-op-proposer
- name: Add A-op-service label
conditions:
- 'files~=^op-service/'
- '#label<5'
actions:
label:
add:
- A-op-service
- name: Add A-op-signer label
conditions:
- 'files~=^op-signer/'
- '#label<5'
actions:
label:
add:
- A-op-signer
- name: Add A-op-wheel label
conditions:
- 'files~=^op-wheel/'
- '#label<5'
actions:
label:
add:
- A-op-wheel
- name: Add A-ops-bedrock label
conditions:
- 'files~=^ops-bedrock/'
- '#label<5'
actions:
label:
add:
- A-ops-bedrock
- name: Add A-ops label
conditions:
- 'files~=^ops/'
- '#label<5'
actions:
label:
add:
- A-ops
- name: Add A-pkg-chain-mon label
conditions:
- 'files~=^packages/chain-mon/'
- '#label<5'
actions: actions:
label: label:
add: add:
- sdk - A-pkg-chain-mon
- name: Add A-pkg-common-ts label and ecopod reviewers
conditions:
- 'files~=^packages/common-ts/'
- '#label<5'
actions:
label:
add:
- A-pkg-common-ts
request_reviews: request_reviews:
users: users:
- roninjin10 - roninjin10
- name: Add common-ts tag and ecopod reviewers - name: Add A-pkg-contracts-bedrock label
conditions: conditions:
- 'files~=^packages/common-ts/' - 'files~=^packages/contracts-bedrock/'
actions:
label:
add:
- A-pkg-contracts-bedrock
- name: Add A-pkg-contracts-ts label
conditions:
- 'files~=^packages/contracts-ts/'
- '#label<5'
actions:
label:
add:
- A-pkg-contracts-ts
- name: Add A-pkg-core-utils label
conditions:
- 'files~=^packages/core-utils/'
- '#label<5'
actions: actions:
label: label:
add: add:
- common-ts - A-pkg-core-utils
- name: Add A-pkg-fee-estimation label
conditions:
- 'files~=^packages/fee-estimation/'
- '#label<5'
actions:
label:
add:
- A-pkg-fee-estimation
- name: Add A-pkg-sdk label and ecopod reviewers
conditions:
- 'files~=^packages/sdk/'
- '#label<5'
actions:
label:
add:
- A-pkg-sdk
request_reviews: request_reviews:
users: users:
- roninjin10 - roninjin10
- name: Add A-pkg-web3js-plugin label
conditions:
- 'files~=^packages/web3js-plugin/'
- '#label<5'
actions:
label:
add:
- A-pkg-web3js-plugin
- name: Add A-proxyd label
conditions:
- 'files~=^proxyd/'
- '#label<5'
actions:
label:
add:
- A-proxyd
- name: Add M-docs label
conditions:
- 'files~=^(docs|specs)\/'
- '#label<5'
actions:
label:
add:
- M-docs
- name: Add M-deletion label when files are removed
conditions:
- 'removed-files~=^/'
actions:
label:
add:
- M-deletion
- name: Add M-ci label when ci files are modified
conditions:
- 'files~=^\.(github|circleci|husky)\/'
- '#label<5'
actions:
label:
add:
- M-ci
...@@ -10,8 +10,9 @@ jobs: ...@@ -10,8 +10,9 @@ jobs:
- uses: actions/stale@v4 - uses: actions/stale@v4
with: with:
stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days.' stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
stale-issue-label: 'S-stale'
exempt-pr-labels: exempt-stale exempt-pr-labels: exempt-stale
days-before-issue-stale: 999 days-before-issue-stale: 999
days-before-pr-stale: 14 days-before-pr-stale: 14
days-before-close: 5 days-before-close: 5
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
...@@ -209,13 +209,20 @@ This makes them a great tool for contributors! ...@@ -209,13 +209,20 @@ This makes them a great tool for contributors!
#### Filtering for Work #### Filtering for Work
To find tickets available for external contribution, take a look at the [`M-community`][M-community] label. To find tickets available for external contribution, take a look at the https://github.com/ethereum-optimism/optimism/labels/M-community label.
You can filter by the [`D-good-first-issue`][D-good-first-issue] You can filter by the https://github.com/ethereum-optimism/optimism/labels/D-good-first-issue
label to find issues that are intended to be easy to implement or fix. label to find issues that are intended to be easy to implement or fix.
Also, all labels can be seen by visiting the [labels page][labels] Also, all labels can be seen by visiting the [labels page][labels]
[labels]: https://github.com/ethereum-optimism/optimism/labels [labels]: https://github.com/ethereum-optimism/optimism/labels
[M-community]: https://github.com/ethereum-optimism/optimism/labels/M-community
[D-good-first-issue]: https://github.com/ethereum-optimism/optimism/labels/D-good-first-issue #### Modifying Labels
When altering label names or deleting labels there are a few things you must be aware of.
- This may affect the mergify bot's use of labels. See the [mergify config](.github/mergify.yml).
- If the https://github.com/ethereum-optimism/labels/S-stale label is altered, the [close-stale](.github/workflows/close-stale.yml) workflow should be updated.
- If the https://github.com/ethereum-optimism/labels/M-dependabot label is altered, the [dependabot config](.github/dependabot.yml) file should be adjusted.
- Saved label filters for project boards will not automatically update. These should be updated if label names change.
...@@ -96,6 +96,10 @@ devnet-up: ...@@ -96,6 +96,10 @@ devnet-up:
# alias for devnet-up # alias for devnet-up
devnet-up-deploy: devnet-up devnet-up-deploy: devnet-up
devnet-test:
PYTHONPATH=./bedrock-devnet python3 ./bedrock-devnet/main.py --monorepo-dir=. --test
.PHONY: devnet-test
devnet-down: devnet-down:
@(cd ./ops-bedrock && GENESIS_TIMESTAMP=$(shell date +%s) docker-compose stop) @(cd ./ops-bedrock && GENESIS_TIMESTAMP=$(shell date +%s) docker-compose stop)
.PHONY: devnet-down .PHONY: devnet-down
......
<div align="center"> <div align="center">
<br /> <br />
<br /> <br />
...@@ -48,13 +47,7 @@ Refer to the Directory Structure section below to understand which packages are ...@@ -48,13 +47,7 @@ Refer to the Directory Structure section below to understand which packages are
## Directory Structure ## Directory Structure
<pre> <pre>
~~ Production ~~ ├── <a href="./docs">docs</a>: A collection of documents including audits and post-mortems
├── <a href="./packages">packages</a>
│ ├── <a href="./packages/common-ts">common-ts</a>: Common tools for building apps in TypeScript
│ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: Bedrock smart contracts.
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ └── <a href="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism
├── <a href="./op-bindings">op-bindings</a>: Go bindings for Bedrock smart contracts. ├── <a href="./op-bindings">op-bindings</a>: Go bindings for Bedrock smart contracts.
├── <a href="./op-batcher">op-batcher</a>: L2-Batch Submitter, submits bundles of batches to L1 ├── <a href="./op-batcher">op-batcher</a>: L2-Batch Submitter, submits bundles of batches to L1
├── <a href="./op-bootnode">op-bootnode</a>: Standalone op-node discovery bootnode ├── <a href="./op-bootnode">op-bootnode</a>: Standalone op-node discovery bootnode
...@@ -70,19 +63,15 @@ Refer to the Directory Structure section below to understand which packages are ...@@ -70,19 +63,15 @@ Refer to the Directory Structure section below to understand which packages are
├── <a href="./op-signer">op-signer</a>: Client signer ├── <a href="./op-signer">op-signer</a>: Client signer
├── <a href="./op-wheel">op-wheel</a>: Database utilities ├── <a href="./op-wheel">op-wheel</a>: Database utilities
├── <a href="./ops-bedrock">ops-bedrock</a>: Bedrock devnet work ├── <a href="./ops-bedrock">ops-bedrock</a>: Bedrock devnet work
├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy
└── <a href="./specs">specs</a>: Specs of the rollup starting at the Bedrock upgrade
~~ Pre-BEDROCK ~~
├── <a href="./packages">packages</a> ├── <a href="./packages">packages</a>
│ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ ├── <a href="./packages/common-ts">common-ts</a>: Common tools for building apps in TypeScript │ ├── <a href="./packages/common-ts">common-ts</a>: Common tools for building apps in TypeScript
│ ├── <a href="./packages/contracts-ts">contracts-ts</a>: ABI and Address constants
│ ├── <a href="./packages/contracts-bedrock">contracts-bedrock</a>: Bedrock smart contracts
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier │ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ └── <a href="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism │ └── <a href="./packages/sdk">sdk</a>: provides a set of tools for interacting with Optimism
├── <a href="./indexer">indexer</a>: indexes and syncs transactions
├── <a href="./op-exporter">op-exporter</a>: A prometheus exporter to collect/serve metrics from an Optimism node
├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy ├── <a href="./proxyd">proxyd</a>: Configurable RPC request router and proxy
└── <a href="./technical-documents">technical-documents</a>: audits and post-mortem documents └── <a href="./specs">specs</a>: Specs of the rollup starting at the Bedrock upgrade
</pre> </pre>
## Branching Model ## Branching Model
......
...@@ -18,6 +18,7 @@ pjoin = os.path.join ...@@ -18,6 +18,7 @@ pjoin = os.path.join
parser = argparse.ArgumentParser(description='Bedrock devnet launcher') parser = argparse.ArgumentParser(description='Bedrock devnet launcher')
parser.add_argument('--monorepo-dir', help='Directory of the monorepo', default=os.getcwd()) parser.add_argument('--monorepo-dir', help='Directory of the monorepo', default=os.getcwd())
parser.add_argument('--allocs', help='Only create the allocs and exit', type=bool, action=argparse.BooleanOptionalAction) parser.add_argument('--allocs', help='Only create the allocs and exit', type=bool, action=argparse.BooleanOptionalAction)
parser.add_argument('--test', help='Tests the deployment, must already be deployed', type=bool, action=argparse.BooleanOptionalAction)
log = logging.getLogger() log = logging.getLogger()
...@@ -57,6 +58,8 @@ def main(): ...@@ -57,6 +58,8 @@ def main():
ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock') ops_bedrock_dir = pjoin(monorepo_dir, 'ops-bedrock')
deploy_config_dir = pjoin(contracts_bedrock_dir, 'deploy-config'), deploy_config_dir = pjoin(contracts_bedrock_dir, 'deploy-config'),
devnet_config_path = pjoin(contracts_bedrock_dir, 'deploy-config', 'devnetL1.json') devnet_config_path = pjoin(contracts_bedrock_dir, 'deploy-config', 'devnetL1.json')
ops_chain_ops = pjoin(monorepo_dir, 'op-chain-ops')
sdk_dir = pjoin(monorepo_dir, 'packages', 'sdk')
paths = Bunch( paths = Bunch(
mono_repo_dir=monorepo_dir, mono_repo_dir=monorepo_dir,
...@@ -68,6 +71,8 @@ def main(): ...@@ -68,6 +71,8 @@ def main():
devnet_config_path=devnet_config_path, devnet_config_path=devnet_config_path,
op_node_dir=op_node_dir, op_node_dir=op_node_dir,
ops_bedrock_dir=ops_bedrock_dir, ops_bedrock_dir=ops_bedrock_dir,
ops_chain_ops=ops_chain_ops,
sdk_dir=sdk_dir,
genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.json'), genesis_l1_path=pjoin(devnet_dir, 'genesis-l1.json'),
genesis_l2_path=pjoin(devnet_dir, 'genesis-l2.json'), genesis_l2_path=pjoin(devnet_dir, 'genesis-l2.json'),
allocs_path=pjoin(devnet_dir, 'allocs-l1.json'), allocs_path=pjoin(devnet_dir, 'allocs-l1.json'),
...@@ -76,6 +81,11 @@ def main(): ...@@ -76,6 +81,11 @@ def main():
rollup_config_path=pjoin(devnet_dir, 'rollup.json') rollup_config_path=pjoin(devnet_dir, 'rollup.json')
) )
if args.test:
log.info('Testing deployed devnet')
devnet_test(paths)
return
os.makedirs(devnet_dir, exist_ok=True) os.makedirs(devnet_dir, exist_ok=True)
if args.allocs: if args.allocs:
...@@ -250,8 +260,26 @@ def wait_for_rpc_server(url): ...@@ -250,8 +260,26 @@ def wait_for_rpc_server(url):
log.info(f'Waiting for RPC server at {url}') log.info(f'Waiting for RPC server at {url}')
time.sleep(1) time.sleep(1)
def devnet_test(paths):
# Check the L2 config
run_command(
['go', 'run', 'cmd/check-l2/main.go', '--l2-rpc-url', 'http://localhost:9545', '--l1-rpc-url', 'http://localhost:8545'],
cwd=paths.ops_chain_ops,
)
run_command(
['npx', 'hardhat', 'deposit-erc20', '--network', 'devnetL1', '--l1-contracts-json-path', paths.addresses_json_path],
cwd=paths.sdk_dir,
timeout=8*60,
)
run_command(
['npx', 'hardhat', 'deposit-eth', '--network', 'devnetL1', '--l1-contracts-json-path', paths.addresses_json_path],
cwd=paths.sdk_dir,
timeout=8*60,
)
def run_command(args, check=True, shell=False, cwd=None, env=None): def run_command(args, check=True, shell=False, cwd=None, env=None, timeout=None):
env = env if env else {} env = env if env else {}
return subprocess.run( return subprocess.run(
args, args,
...@@ -261,7 +289,8 @@ def run_command(args, check=True, shell=False, cwd=None, env=None): ...@@ -261,7 +289,8 @@ def run_command(args, check=True, shell=False, cwd=None, env=None):
**os.environ, **os.environ,
**env **env
}, },
cwd=cwd cwd=cwd,
timeout=timeout
) )
......
...@@ -17,14 +17,12 @@ require ( ...@@ -17,14 +17,12 @@ require (
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/golang-lru v1.0.2
github.com/hashicorp/golang-lru/v2 v2.0.2 github.com/hashicorp/golang-lru/v2 v2.0.2
github.com/holiman/uint256 v1.2.3 github.com/holiman/uint256 v1.2.3
github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-ds-leveldb v0.5.0 github.com/ipfs/go-ds-leveldb v0.5.0
github.com/jackc/pgtype v1.14.0 github.com/jackc/pgtype v1.14.0
github.com/jackc/pgx/v5 v5.4.3 github.com/jackc/pgx/v5 v5.4.3
github.com/joho/godotenv v1.5.1
github.com/libp2p/go-libp2p v0.27.8 github.com/libp2p/go-libp2p v0.27.8
github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-pubsub v0.9.3
github.com/libp2p/go-libp2p-testing v0.12.0 github.com/libp2p/go-libp2p-testing v0.12.0
...@@ -33,6 +31,7 @@ require ( ...@@ -33,6 +31,7 @@ require (
github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multiaddr v0.10.1
github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.27.4
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0 github.com/pkg/profile v1.7.0
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.14.0
...@@ -67,23 +66,29 @@ require ( ...@@ -67,23 +66,29 @@ require (
github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20220523130400-f11357ae11c7 // indirect
github.com/crate-crypto/go-kzg-4844 v0.2.0 // indirect github.com/crate-crypto/go-kzg-4844 v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/docker/docker v20.10.24+incompatible // indirect github.com/docker/docker v20.10.24+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 // indirect
github.com/elastic/gosigar v0.14.2 // indirect github.com/elastic/gosigar v0.14.2 // indirect
github.com/ethereum/c-kzg-4844 v0.2.0 // indirect github.com/ethereum/c-kzg-4844 v0.2.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect github.com/felixge/fgprof v0.9.3 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/flynn/noise v1.0.0 // indirect github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/gballet/go-verkle v0.0.0-20220902153445-097bd83b7732 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
...@@ -111,8 +116,10 @@ require ( ...@@ -111,8 +116,10 @@ require (
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect github.com/jbenet/goprocess v0.1.4 // indirect
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/karalabe/usb v0.0.2 // indirect
github.com/klauspost/compress v1.16.4 // indirect github.com/klauspost/compress v1.16.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect github.com/koron/go-ssdp v0.0.4 // indirect
...@@ -148,6 +155,8 @@ require ( ...@@ -148,6 +155,8 @@ require (
github.com/multiformats/go-multihash v0.2.1 // indirect github.com/multiformats/go-multihash v0.2.1 // indirect
github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect github.com/multiformats/go-varint v0.0.7 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 // indirect
github.com/onsi/ginkgo/v2 v2.9.2 // indirect github.com/onsi/ginkgo/v2 v2.9.2 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect github.com/opencontainers/runtime-spec v1.0.2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
......
This diff is collapsed.
...@@ -22,4 +22,4 @@ FROM alpine:3.16 ...@@ -22,4 +22,4 @@ FROM alpine:3.16
COPY --from=builder /app/indexer/indexer /usr/local/bin COPY --from=builder /app/indexer/indexer /usr/local/bin
CMD ["indexer"] CMD ["indexer", "--config", "/app/indexer/indexer.toml"]
...@@ -2,6 +2,15 @@ ...@@ -2,6 +2,15 @@
## Getting started ## Getting started
### Setup env
The `indexer.toml` stores a set of preset environmental variables that can be used to run the indexer with the exception of the network specific `l1-rpc` and `l2-rpc` variables. The `indexer.toml` file can be ran as a default config, otherwise a custom `.toml` config can provided via the `--config` flag when running the application. Additionally, L1 system contract addresses must provided for the specific OP Stack network actively being indexed. Currently the indexer has no way to infer L1 system config addresses provided a L2 chain ID or network enum.
### Testing
All tests can be ran by running `make test` from the `/indexer` directory. This will run all unit and e2e tests.
**NOTE:** Successfully running the E2E tests requires spinning up a local L1 geth node and pre-populating it with necessary bedrock genesis state. This can be done by calling `make devnet-allocs` from the root of the optimism monorepo before running the indexer tests. More information on this can be found in the [op-e2e README](../op-e2e/README.md).
### Run indexer vs goerli ### Run indexer vs goerli
- install docker - install docker
......
...@@ -2,7 +2,6 @@ package api ...@@ -2,7 +2,6 @@ package api
import ( import (
"fmt" "fmt"
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
...@@ -21,17 +20,21 @@ var mockAddress = "0x4204204204204204204204204204204204204204" ...@@ -21,17 +20,21 @@ var mockAddress = "0x4204204204204204204204204204204204204204"
var ( var (
deposit = database.L1BridgeDeposit{ deposit = database.L1BridgeDeposit{
TransactionSourceHash: common.HexToHash("abc"), TransactionSourceHash: common.HexToHash("abc"),
CrossDomainMessengerNonce: &database.U256{Int: big.NewInt(0)}, BridgeTransfer: database.BridgeTransfer{
Tx: database.Transaction{}, CrossDomainMessageHash: &common.Hash{},
TokenPair: database.TokenPair{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{},
},
} }
withdrawal = database.L2BridgeWithdrawal{ withdrawal = database.L2BridgeWithdrawal{
TransactionWithdrawalHash: common.HexToHash("0x420"), TransactionWithdrawalHash: common.HexToHash("0x420"),
CrossDomainMessengerNonce: &database.U256{Int: big.NewInt(0)}, BridgeTransfer: database.BridgeTransfer{
Tx: database.Transaction{}, CrossDomainMessageHash: &common.Hash{},
TokenPair: database.TokenPair{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{},
},
} }
) )
...@@ -39,36 +42,39 @@ func (mbv *MockBridgeTransfersView) L1BridgeDeposit(hash common.Hash) (*database ...@@ -39,36 +42,39 @@ func (mbv *MockBridgeTransfersView) L1BridgeDeposit(hash common.Hash) (*database
return &deposit, nil return &deposit, nil
} }
func (mbv *MockBridgeTransfersView) L1BridgeDepositByCrossDomainMessengerNonce(nonce *big.Int) (*database.L1BridgeDeposit, error) { func (mbv *MockBridgeTransfersView) L1BridgeDepositWithFilter(filter database.BridgeTransfer) (*database.L1BridgeDeposit, error) {
return &deposit, nil return &deposit, nil
} }
func (mbv *MockBridgeTransfersView) L1BridgeDepositsByAddress(address common.Address) ([]*database.L1BridgeDepositWithTransactionHashes, error) {
return []*database.L1BridgeDepositWithTransactionHashes{
{
L1BridgeDeposit: deposit,
L1TransactionHash: common.HexToHash("0x123"),
},
}, nil
}
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawal(address common.Hash) (*database.L2BridgeWithdrawal, error) { func (mbv *MockBridgeTransfersView) L2BridgeWithdrawal(address common.Hash) (*database.L2BridgeWithdrawal, error) {
return &withdrawal, nil return &withdrawal, nil
} }
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalByCrossDomainMessengerNonce(nonce *big.Int) (*database.L2BridgeWithdrawal, error) { func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalWithFilter(filter database.BridgeTransfer) (*database.L2BridgeWithdrawal, error) {
return &withdrawal, nil return &withdrawal, nil
} }
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.Address) ([]*database.L2BridgeWithdrawalWithTransactionHashes, error) { func (mbv *MockBridgeTransfersView) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*database.L1BridgeDepositsResponse, error) {
return []*database.L2BridgeWithdrawalWithTransactionHashes{ return &database.L1BridgeDepositsResponse{
{ Deposits: []database.L1BridgeDepositWithTransactionHashes{
L2BridgeWithdrawal: withdrawal, {
L2TransactionHash: common.HexToHash("0x789"), L1BridgeDeposit: deposit,
L1TransactionHash: common.HexToHash("0x123"),
},
}, },
}, nil }, nil
} }
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.Address, cursor string, limit int) (*database.L2BridgeWithdrawalsResponse, error) {
return &database.L2BridgeWithdrawalsResponse{
Withdrawals: []database.L2BridgeWithdrawalWithTransactionHashes{
{
L2BridgeWithdrawal: withdrawal,
L2TransactionHash: common.HexToHash("0x789"),
},
},
}, nil
}
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
api := NewApi(&MockBridgeTransfersView{}, logger) api := NewApi(&MockBridgeTransfersView{}, logger)
......
...@@ -2,6 +2,7 @@ package routes ...@@ -2,6 +2,7 @@ package routes
import ( import (
"net/http" "net/http"
"strconv"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -29,9 +30,9 @@ type DepositResponse struct { ...@@ -29,9 +30,9 @@ type DepositResponse struct {
// TODO this is original spec but maybe include the l2 block info too for the relayed tx // TODO this is original spec but maybe include the l2 block info too for the relayed tx
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse // FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashes) DepositResponse { func newDepositResponse(deposits *database.L1BridgeDepositsResponse) DepositResponse {
items := make([]DepositItem, len(deposits)) items := make([]DepositItem, len(deposits.Deposits))
for _, deposit := range deposits { for _, deposit := range deposits.Deposits {
item := DepositItem{ item := DepositItem{
Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(), Guid: deposit.L1BridgeDeposit.TransactionSourceHash.String(),
Block: Block{ Block: Block{
...@@ -48,7 +49,7 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe ...@@ -48,7 +49,7 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe
Amount: deposit.L1BridgeDeposit.Tx.Amount.Int.String(), Amount: deposit.L1BridgeDeposit.Tx.Amount.Int.String(),
L1Token: TokenInfo{ L1Token: TokenInfo{
ChainId: 1, ChainId: 1,
Address: deposit.L1BridgeDeposit.TokenPair.L1TokenAddress.String(), Address: deposit.L1BridgeDeposit.TokenPair.LocalTokenAddress.String(),
Name: "TODO", Name: "TODO",
Symbol: "TODO", Symbol: "TODO",
Decimals: 420, Decimals: 420,
...@@ -58,7 +59,7 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe ...@@ -58,7 +59,7 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe
}, },
L2Token: TokenInfo{ L2Token: TokenInfo{
ChainId: 10, ChainId: 10,
Address: deposit.L1BridgeDeposit.TokenPair.L2TokenAddress.String(), Address: deposit.L1BridgeDeposit.TokenPair.RemoteTokenAddress.String(),
Name: "TODO", Name: "TODO",
Symbol: "TODO", Symbol: "TODO",
Decimals: 420, Decimals: 420,
...@@ -71,16 +72,30 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe ...@@ -71,16 +72,30 @@ func newDepositResponse(deposits []*database.L1BridgeDepositWithTransactionHashe
} }
return DepositResponse{ return DepositResponse{
Cursor: "42042042-4204-4204-4204-420420420420", // TODO Cursor: deposits.Cursor,
HasNextPage: false, // TODO HasNextPage: deposits.HasNextPage,
Items: items, Items: items,
} }
} }
func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) { func (h Routes) L1DepositsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address")) address := common.HexToAddress(chi.URLParam(r, "address"))
cursor := r.URL.Query().Get("cursor")
limitQuery := r.URL.Query().Get("limit")
deposits, err := h.BridgeTransfersView.L1BridgeDepositsByAddress(address) defaultLimit := 100
limit := defaultLimit
if limitQuery != "" {
parsedLimit, err := strconv.Atoi(limitQuery)
if err != nil {
http.Error(w, "Limit could not be parsed into a number", http.StatusBadRequest)
h.Logger.Error("Invalid limit")
h.Logger.Error(err.Error())
}
limit = parsedLimit
}
deposits, err := h.BridgeTransfersView.L1BridgeDepositsByAddress(address, cursor, limit)
if err != nil { if err != nil {
http.Error(w, "Internal server error reading deposits", http.StatusInternalServerError) http.Error(w, "Internal server error reading deposits", http.StatusInternalServerError)
h.Logger.Error("Unable to read deposits from DB") h.Logger.Error("Unable to read deposits from DB")
......
...@@ -2,6 +2,7 @@ package routes ...@@ -2,6 +2,7 @@ package routes
import ( import (
"net/http" "net/http"
"strconv"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -42,9 +43,9 @@ type WithdrawalResponse struct { ...@@ -42,9 +43,9 @@ type WithdrawalResponse struct {
} }
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse // FIXME make a pure function that returns a struct instead of newWithdrawalResponse
func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransactionHashes) WithdrawalResponse { func newWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
items := make([]WithdrawalItem, len(withdrawals)) items := make([]WithdrawalItem, len(withdrawals.Withdrawals))
for _, withdrawal := range withdrawals { for _, withdrawal := range withdrawals.Withdrawals {
item := WithdrawalItem{ item := WithdrawalItem{
Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(), Guid: withdrawal.L2BridgeWithdrawal.TransactionWithdrawalHash.String(),
Block: Block{ Block: Block{
...@@ -73,7 +74,7 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac ...@@ -73,7 +74,7 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac
WithdrawalState: "COMPLETE", // TODO WithdrawalState: "COMPLETE", // TODO
L1Token: TokenInfo{ L1Token: TokenInfo{
ChainId: 1, ChainId: 1,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.L1TokenAddress.String(), Address: withdrawal.L2BridgeWithdrawal.TokenPair.RemoteTokenAddress.String(),
Name: "Example", // TODO Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO Decimals: 18, // TODO
...@@ -83,7 +84,7 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac ...@@ -83,7 +84,7 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac
}, },
L2Token: TokenInfo{ L2Token: TokenInfo{
ChainId: 10, ChainId: 10,
Address: withdrawal.L2BridgeWithdrawal.TokenPair.L2TokenAddress.String(), Address: withdrawal.L2BridgeWithdrawal.TokenPair.LocalTokenAddress.String(),
Name: "Example", // TODO Name: "Example", // TODO
Symbol: "EXAMPLE", // TODO Symbol: "EXAMPLE", // TODO
Decimals: 18, // TODO Decimals: 18, // TODO
...@@ -96,23 +97,34 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac ...@@ -96,23 +97,34 @@ func newWithdrawalResponse(withdrawals []*database.L2BridgeWithdrawalWithTransac
} }
return WithdrawalResponse{ return WithdrawalResponse{
Cursor: "42042042-0420-4204-2042-420420420420", // TODO Cursor: withdrawals.Cursor,
HasNextPage: true, // TODO HasNextPage: withdrawals.HasNextPage,
Items: items, Items: items,
} }
} }
func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) { func (h Routes) L2WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
address := common.HexToAddress(chi.URLParam(r, "address")) address := common.HexToAddress(chi.URLParam(r, "address"))
cursor := r.URL.Query().Get("cursor")
limitQuery := r.URL.Query().Get("limit")
withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address) defaultLimit := 100
limit := defaultLimit
if limitQuery != "" {
parsedLimit, err := strconv.Atoi(limitQuery)
if err != nil {
http.Error(w, "Limit could not be parsed into a number", http.StatusBadRequest)
h.Logger.Error("Invalid limit")
h.Logger.Error(err.Error())
}
limit = parsedLimit
}
withdrawals, err := h.BridgeTransfersView.L2BridgeWithdrawalsByAddress(address, cursor, limit)
if err != nil { if err != nil {
http.Error(w, "Internal server error fetching withdrawals", http.StatusInternalServerError) http.Error(w, "Internal server error reading withdrawals", http.StatusInternalServerError)
h.Logger.Error("Unable to read deposits from DB") h.Logger.Error("Unable to read withdrawals from DB")
h.Logger.Error(err.Error()) h.Logger.Error(err.Error())
return
} }
response := newWithdrawalResponse(withdrawals) response := newWithdrawalResponse(withdrawals)
jsonResponse(w, h.Logger, response, http.StatusOK) jsonResponse(w, h.Logger, response, http.StatusOK)
......
...@@ -40,7 +40,7 @@ func runIndexer(ctx *cli.Context) error { ...@@ -40,7 +40,7 @@ func runIndexer(ctx *cli.Context) error {
return err return err
} }
indexer, err := indexer.NewIndexer(cfg.Chain, cfg.RPCs, db, logger) indexer, err := indexer.NewIndexer(logger, cfg.Chain, cfg.RPCs, db)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -3,12 +3,10 @@ package config ...@@ -3,12 +3,10 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
"reflect"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
geth_log "github.com/ethereum/go-ethereum/log" geth_log "github.com/ethereum/go-ethereum/log"
"github.com/joho/godotenv"
) )
// in future presets can just be onchain config and fetched on initialization // in future presets can just be onchain config and fetched on initialization
...@@ -24,37 +22,27 @@ type Config struct { ...@@ -24,37 +22,27 @@ type Config struct {
// fetch this via onchain config from RPCsConfig and remove from config in future // fetch this via onchain config from RPCsConfig and remove from config in future
type L1Contracts struct { type L1Contracts struct {
OptimismPortal common.Address OptimismPortalProxy common.Address `toml:"optimism-portal"`
L2OutputOracle common.Address L2OutputOracleProxy common.Address `toml:"l2-output-oracle"`
L1CrossDomainMessenger common.Address L1CrossDomainMessengerProxy common.Address `toml:"l1-cross-domain-messenger"`
L1StandardBridge common.Address L1StandardBridgeProxy common.Address `toml:"l1-standard-bridge"`
L1ERC721Bridge common.Address
// Some more contracts -- ProxyAdmin, SystemConfig, etcc // Some more contracts -- L1ERC721Bridge, ProxyAdmin, SystemConfig, etc
// Ignore the auxiliary contracts? // Ignore the auxiliary contracts?
// Legacy contracts? We'll add this in to index the legacy chain. // Legacy contracts? We'll add this in to index the legacy chain.
// Remove afterwards? // Remove afterwards?
} }
func (c L1Contracts) ToSlice() []common.Address {
fields := reflect.VisibleFields(reflect.TypeOf(c))
v := reflect.ValueOf(c)
contracts := make([]common.Address, len(fields))
for i, field := range fields {
contracts[i] = (v.FieldByName(field.Name).Interface()).(common.Address)
}
return contracts
}
// ChainConfig configures of the chain being indexed // ChainConfig configures of the chain being indexed
type ChainConfig struct { type ChainConfig struct {
// Configure known chains with the l2 chain id // Configure known chains with the l2 chain id
Preset int // NOTE - This currently performs no lookups to extract known L1 contracts by l2 chain id
// Configure custom chains via providing the L1Contract addresses Preset int
L1Contracts L1Contracts L1Contracts L1Contracts `toml:"l1-contracts"`
// L1StartingHeight is the block height to start indexing from
// NOTE - This is currently unimplemented
L1StartingHeight int
} }
// RPCsConfig configures the RPC urls // RPCsConfig configures the RPC urls
...@@ -86,13 +74,7 @@ type MetricsConfig struct { ...@@ -86,13 +74,7 @@ type MetricsConfig struct {
// LoadConfig loads the `indexer.toml` config file from a given path // LoadConfig loads the `indexer.toml` config file from a given path
func LoadConfig(logger geth_log.Logger, path string) (Config, error) { func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
if err := godotenv.Load(); err != nil { logger.Info("Loading config file", "path", path)
logger.Warn("Unable to load .env file", err)
logger.Info("Continuing without .env file")
} else {
logger.Info("Loaded .env file")
}
var conf Config var conf Config
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
...@@ -102,6 +84,8 @@ func LoadConfig(logger geth_log.Logger, path string) (Config, error) { ...@@ -102,6 +84,8 @@ func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
data = []byte(os.ExpandEnv(string(data))) data = []byte(os.ExpandEnv(string(data)))
logger.Debug("Decoding config file", "data", string(data))
if _, err := toml.Decode(string(data), &conf); err != nil { if _, err := toml.Decode(string(data), &conf); err != nil {
logger.Info("Failed to decode config file", "message", err) logger.Info("Failed to decode config file", "message", err)
return conf, err return conf, err
......
...@@ -54,11 +54,10 @@ func TestLoadConfig(t *testing.T) { ...@@ -54,11 +54,10 @@ func TestLoadConfig(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, conf.Chain.Preset, 420) require.Equal(t, conf.Chain.Preset, 420)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortal.String(), presetL1Contracts[420].OptimismPortal.String()) require.Equal(t, conf.Chain.L1Contracts.OptimismPortalProxy.String(), presetL1Contracts[420].OptimismPortalProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessenger.String(), presetL1Contracts[420].L1CrossDomainMessenger.String()) require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessengerProxy.String(), presetL1Contracts[420].L1CrossDomainMessengerProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1ERC721Bridge.String(), presetL1Contracts[420].L1ERC721Bridge.String()) require.Equal(t, conf.Chain.L1Contracts.L1StandardBridgeProxy.String(), presetL1Contracts[420].L1StandardBridgeProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridge.String(), presetL1Contracts[420].L1StandardBridge.String()) require.Equal(t, conf.Chain.L1Contracts.L2OutputOracleProxy.String(), presetL1Contracts[420].L2OutputOracleProxy.String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracle.String(), presetL1Contracts[420].L2OutputOracle.String())
require.Equal(t, conf.RPCs.L1RPC, "https://l1.example.com") require.Equal(t, conf.RPCs.L1RPC, "https://l1.example.com")
require.Equal(t, conf.RPCs.L2RPC, "https://l2.example.com") require.Equal(t, conf.RPCs.L2RPC, "https://l2.example.com")
require.Equal(t, conf.DB.Host, "127.0.0.1") require.Equal(t, conf.DB.Host, "127.0.0.1")
...@@ -80,7 +79,11 @@ func TestLoadConfig_WithoutPreset(t *testing.T) { ...@@ -80,7 +79,11 @@ func TestLoadConfig_WithoutPreset(t *testing.T) {
testData := ` testData := `
[chain] [chain]
l1contracts = { OptimismPortal = "0x4205Fc579115071764c7423A4f12eDde41f106Ed", L2OutputOracle = "0x42097868233d1aa22e815a266982f2cf17685a27", L1CrossDomainMessenger = "0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1", L1StandardBridge = "0x4209fc46f92E8a1c0deC1b1747d010903E884bE1", L1ERC721Bridge ="0x420749f83b81B301cAb5f48EB8516B986DAef23D" } [chain.l1-contracts]
optimism-portal = "0x4205Fc579115071764c7423A4f12eDde41f106Ed"
l2-output-oracle = "0x42097868233d1aa22e815a266982f2cf17685a27"
l1-cross-domain-messenger = "0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1"
l1-standard-bridge = "0x4209fc46f92E8a1c0deC1b1747d010903E884bE1"
[rpcs] [rpcs]
l1-rpc = "https://l1.example.com" l1-rpc = "https://l1.example.com"
...@@ -99,11 +102,10 @@ func TestLoadConfig_WithoutPreset(t *testing.T) { ...@@ -99,11 +102,10 @@ func TestLoadConfig_WithoutPreset(t *testing.T) {
conf, err := LoadConfig(logger, tmpfile.Name()) conf, err := LoadConfig(logger, tmpfile.Name())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, conf.Chain.L1Contracts.OptimismPortal.String(), common.HexToAddress("0x4205Fc579115071764c7423A4f12eDde41f106Ed").String()) require.Equal(t, conf.Chain.L1Contracts.OptimismPortalProxy.String(), common.HexToAddress("0x4205Fc579115071764c7423A4f12eDde41f106Ed").String())
require.Equal(t, conf.Chain.L1Contracts.L2OutputOracle.String(), common.HexToAddress("0x42097868233d1aa22e815a266982f2cf17685a27").String()) require.Equal(t, conf.Chain.L1Contracts.L2OutputOracleProxy.String(), common.HexToAddress("0x42097868233d1aa22e815a266982f2cf17685a27").String())
require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessenger.String(), common.HexToAddress("0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1").String()) require.Equal(t, conf.Chain.L1Contracts.L1CrossDomainMessengerProxy.String(), common.HexToAddress("0x420ce71c97B33Cc4729CF772ae268934F7ab5fA1").String())
require.Equal(t, conf.Chain.L1Contracts.L1StandardBridge.String(), common.HexToAddress("0x4209fc46f92E8a1c0deC1b1747d010903E884bE1").String()) require.Equal(t, conf.Chain.L1Contracts.L1StandardBridgeProxy.String(), common.HexToAddress("0x4209fc46f92E8a1c0deC1b1747d010903E884bE1").String())
require.Equal(t, conf.Chain.L1Contracts.L1ERC721Bridge.String(), common.HexToAddress("0x420749f83b81B301cAb5f48EB8516B986DAef23D").String())
require.Equal(t, conf.Chain.Preset, 0) require.Equal(t, conf.Chain.Preset, 0)
} }
......
...@@ -10,54 +10,44 @@ import ( ...@@ -10,54 +10,44 @@ import (
var presetL1Contracts = map[int]L1Contracts{ var presetL1Contracts = map[int]L1Contracts{
// OP Mainnet // OP Mainnet
10: { 10: {
OptimismPortal: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), OptimismPortalProxy: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"),
L2OutputOracle: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"), L2OutputOracleProxy: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessenger: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"), L1CrossDomainMessengerProxy: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridge: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"), L1StandardBridgeProxy: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"),
L1ERC721Bridge: common.HexToAddress("0x5a7749f83b81B301cAb5f48EB8516B986DAef23D"),
}, },
// OP Goerli // OP Goerli
420: { 420: {
OptimismPortal: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"), OptimismPortalProxy: common.HexToAddress("0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383"),
L2OutputOracle: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"), L2OutputOracleProxy: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"),
L1CrossDomainMessenger: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"), L1CrossDomainMessengerProxy: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"),
L1StandardBridge: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"), L1StandardBridgeProxy: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"),
L1ERC721Bridge: common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"),
}, },
// Base Mainnet // Base Mainnet
8453: { 8453: {
OptimismPortal: common.HexToAddress("0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"), OptimismPortalProxy: common.HexToAddress("0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"),
L2OutputOracle: common.HexToAddress("0x56315b90c40730925ec5485cf004d835058518A0"), L2OutputOracleProxy: common.HexToAddress("0x56315b90c40730925ec5485cf004d835058518A0"),
L1CrossDomainMessenger: common.HexToAddress("0x866E82a600A1414e583f7F13623F1aC5d58b0Afa"), L1CrossDomainMessengerProxy: common.HexToAddress("0x866E82a600A1414e583f7F13623F1aC5d58b0Afa"),
L1StandardBridge: common.HexToAddress("0x3154Cf16ccdb4C6d922629664174b904d80F2C35"), L1StandardBridgeProxy: common.HexToAddress("0x3154Cf16ccdb4C6d922629664174b904d80F2C35"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
}, },
// Base Goerli // Base Goerli
84531: { 84531: {
OptimismPortal: common.HexToAddress("0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA"), OptimismPortalProxy: common.HexToAddress("0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA"),
L2OutputOracle: common.HexToAddress("0x2A35891ff30313CcFa6CE88dcf3858bb075A2298"), L2OutputOracleProxy: common.HexToAddress("0x2A35891ff30313CcFa6CE88dcf3858bb075A2298"),
L1CrossDomainMessenger: common.HexToAddress("0x8e5693140eA606bcEB98761d9beB1BC87383706D"), L1CrossDomainMessengerProxy: common.HexToAddress("0x8e5693140eA606bcEB98761d9beB1BC87383706D"),
L1StandardBridge: common.HexToAddress("0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a"), L1StandardBridgeProxy: common.HexToAddress("0xfA6D8Ee5BE770F84FC001D098C4bD604Fe01284a"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
}, },
// Zora mainnet // Zora mainnet
7777777: { 7777777: {
OptimismPortal: common.HexToAddress("0x1a0ad011913A150f69f6A19DF447A0CfD9551054"), OptimismPortalProxy: common.HexToAddress("0x1a0ad011913A150f69f6A19DF447A0CfD9551054"),
L2OutputOracle: common.HexToAddress("0x9E6204F750cD866b299594e2aC9eA824E2e5f95c"), L2OutputOracleProxy: common.HexToAddress("0x9E6204F750cD866b299594e2aC9eA824E2e5f95c"),
L1CrossDomainMessenger: common.HexToAddress("0xdC40a14d9abd6F410226f1E6de71aE03441ca506"), L1CrossDomainMessengerProxy: common.HexToAddress("0xdC40a14d9abd6F410226f1E6de71aE03441ca506"),
L1StandardBridge: common.HexToAddress("0x3e2Ea9B92B7E48A52296fD261dc26fd995284631"), L1StandardBridgeProxy: common.HexToAddress("0x3e2Ea9B92B7E48A52296fD261dc26fd995284631"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
}, },
// Zora goerli // Zora goerli
999: { 999: {
OptimismPortal: common.HexToAddress("0xDb9F51790365e7dc196e7D072728df39Be958ACe"), OptimismPortalProxy: common.HexToAddress("0xDb9F51790365e7dc196e7D072728df39Be958ACe"),
L2OutputOracle: common.HexToAddress("0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00"), L2OutputOracleProxy: common.HexToAddress("0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00"),
L1CrossDomainMessenger: common.HexToAddress("0xD87342e16352D33170557A7dA1e5fB966a60FafC"), L1CrossDomainMessengerProxy: common.HexToAddress("0xD87342e16352D33170557A7dA1e5fB966a60FafC"),
L1StandardBridge: common.HexToAddress("0x7CC09AC2452D6555d5e0C213Ab9E2d44eFbFc956"), L1StandardBridgeProxy: common.HexToAddress("0x7CC09AC2452D6555d5e0C213Ab9E2d44eFbFc956"),
// FIXME update this to the correct address
L1ERC721Bridge: common.HexToAddress("0x0000000000000000000000000000000000000000"),
}, },
} }
...@@ -38,11 +38,11 @@ func BlockHeaderFromHeader(header *types.Header) BlockHeader { ...@@ -38,11 +38,11 @@ func BlockHeaderFromHeader(header *types.Header) BlockHeader {
} }
type L1BlockHeader struct { type L1BlockHeader struct {
BlockHeader BlockHeader `gorm:"embedded"`
} }
type L2BlockHeader struct { type L2BlockHeader struct {
BlockHeader BlockHeader `gorm:"embedded"`
} }
type LegacyStateBatch struct { type LegacyStateBatch struct {
...@@ -65,24 +65,28 @@ type OutputProposal struct { ...@@ -65,24 +65,28 @@ type OutputProposal struct {
} }
type BlocksView interface { type BlocksView interface {
L1BlockHeader(*big.Int) (*L1BlockHeader, error) L1BlockHeader(common.Hash) (*L1BlockHeader, error)
LatestL1BlockHeader() (*L1BlockHeader, error) L1BlockHeaderWithFilter(BlockHeader) (*L1BlockHeader, error)
L1LatestBlockHeader() (*L1BlockHeader, error)
L2BlockHeader(common.Hash) (*L2BlockHeader, error)
L2BlockHeaderWithFilter(BlockHeader) (*L2BlockHeader, error)
L2LatestBlockHeader() (*L2BlockHeader, error)
LatestCheckpointedOutput() (*OutputProposal, error) LatestCheckpointedOutput() (*OutputProposal, error)
OutputProposal(index *big.Int) (*OutputProposal, error) OutputProposal(index *big.Int) (*OutputProposal, error)
L2BlockHeader(*big.Int) (*L2BlockHeader, error) LatestEpoch() (*Epoch, error)
LatestL2BlockHeader() (*L2BlockHeader, error)
} }
type BlocksDB interface { type BlocksDB interface {
BlocksView BlocksView
StoreL1BlockHeaders([]*L1BlockHeader) error StoreL1BlockHeaders([]L1BlockHeader) error
StoreL2BlockHeaders([]*L2BlockHeader) error StoreL2BlockHeaders([]L2BlockHeader) error
StoreLegacyStateBatches([]*LegacyStateBatch) error StoreLegacyStateBatches([]LegacyStateBatch) error
StoreOutputProposals([]*OutputProposal) error StoreOutputProposals([]OutputProposal) error
} }
/** /**
...@@ -99,36 +103,39 @@ func newBlocksDB(db *gorm.DB) BlocksDB { ...@@ -99,36 +103,39 @@ func newBlocksDB(db *gorm.DB) BlocksDB {
// L1 // L1
func (db *blocksDB) StoreL1BlockHeaders(headers []*L1BlockHeader) error { func (db *blocksDB) StoreL1BlockHeaders(headers []L1BlockHeader) error {
result := db.gorm.Create(&headers) result := db.gorm.Create(&headers)
return result.Error return result.Error
} }
func (db *blocksDB) StoreLegacyStateBatches(stateBatches []*LegacyStateBatch) error { func (db *blocksDB) StoreLegacyStateBatches(stateBatches []LegacyStateBatch) error {
result := db.gorm.Create(stateBatches) result := db.gorm.Create(stateBatches)
return result.Error return result.Error
} }
func (db *blocksDB) StoreOutputProposals(outputs []*OutputProposal) error { func (db *blocksDB) StoreOutputProposals(outputs []OutputProposal) error {
result := db.gorm.Create(outputs) result := db.gorm.Create(outputs)
return result.Error return result.Error
} }
func (db *blocksDB) L1BlockHeader(height *big.Int) (*L1BlockHeader, error) { func (db *blocksDB) L1BlockHeader(hash common.Hash) (*L1BlockHeader, error) {
return db.L1BlockHeaderWithFilter(BlockHeader{Hash: hash})
}
func (db *blocksDB) L1BlockHeaderWithFilter(filter BlockHeader) (*L1BlockHeader, error) {
var l1Header L1BlockHeader var l1Header L1BlockHeader
result := db.gorm.Where(&BlockHeader{Number: U256{Int: height}}).Take(&l1Header) result := db.gorm.Where(&filter).Take(&l1Header)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
} }
return nil, result.Error return nil, result.Error
} }
return &l1Header, nil return &l1Header, nil
} }
func (db *blocksDB) LatestL1BlockHeader() (*L1BlockHeader, error) { func (db *blocksDB) L1LatestBlockHeader() (*L1BlockHeader, error) {
var l1Header L1BlockHeader var l1Header L1BlockHeader
result := db.gorm.Order("number DESC").Take(&l1Header) result := db.gorm.Order("number DESC").Take(&l1Header)
if result.Error != nil { if result.Error != nil {
...@@ -172,36 +179,71 @@ func (db *blocksDB) OutputProposal(index *big.Int) (*OutputProposal, error) { ...@@ -172,36 +179,71 @@ func (db *blocksDB) OutputProposal(index *big.Int) (*OutputProposal, error) {
// L2 // L2
func (db *blocksDB) StoreL2BlockHeaders(headers []*L2BlockHeader) error { func (db *blocksDB) StoreL2BlockHeaders(headers []L2BlockHeader) error {
result := db.gorm.Create(&headers) result := db.gorm.Create(&headers)
return result.Error return result.Error
} }
func (db *blocksDB) L2BlockHeader(height *big.Int) (*L2BlockHeader, error) { func (db *blocksDB) L2BlockHeader(hash common.Hash) (*L2BlockHeader, error) {
return db.L2BlockHeaderWithFilter(BlockHeader{Hash: hash})
}
func (db *blocksDB) L2BlockHeaderWithFilter(filter BlockHeader) (*L2BlockHeader, error) {
var l2Header L2BlockHeader var l2Header L2BlockHeader
result := db.gorm.Where(&BlockHeader{Number: U256{Int: height}}).Take(&l2Header) result := db.gorm.Where(&filter).Take(&l2Header)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
} }
return nil, result.Error return nil, result.Error
} }
return &l2Header, nil return &l2Header, nil
} }
func (db *blocksDB) LatestL2BlockHeader() (*L2BlockHeader, error) { func (db *blocksDB) L2LatestBlockHeader() (*L2BlockHeader, error) {
var l2Header L2BlockHeader var l2Header L2BlockHeader
result := db.gorm.Order("number DESC").Take(&l2Header) result := db.gorm.Order("number DESC").Take(&l2Header)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
} }
return nil, result.Error return nil, result.Error
} }
result.Logger.Info(context.Background(), "number ", l2Header.Number) result.Logger.Info(context.Background(), "number ", l2Header.Number)
return &l2Header, nil return &l2Header, nil
} }
// Auxiliary Methods on both L1 & L2
type Epoch struct {
L1BlockHeader L1BlockHeader `gorm:"embedded"`
L2BlockHeader L2BlockHeader `gorm:"embedded"`
}
// LatestEpoch return the latest epoch, seen on L1 & L2. In other words
// this returns the latest indexed L1 block that has a corresponding
// indexed L2 block with a matching L1Origin (equal timestamps).
//
// For more, see the protocol spec:
// - https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md
func (db *blocksDB) LatestEpoch() (*Epoch, error) {
// Since L1 blocks occur less frequently than L2, we do a INNER JOIN from L1 on
// L2 for a faster query. Per the protocol, the L2 block that starts a new epoch
// will have a matching timestamp with the L1 origin.
query := db.gorm.Table("l1_block_headers").Order("l1_block_headers.timestamp DESC")
query = query.Joins("INNER JOIN l2_block_headers ON l1_block_headers.timestamp = l2_block_headers.timestamp")
query = query.Select("*")
var epoch Epoch
result := query.Take(&epoch)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &epoch, nil
}
...@@ -3,7 +3,6 @@ package database ...@@ -3,7 +3,6 @@ package database
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"gorm.io/gorm" "gorm.io/gorm"
...@@ -17,8 +16,8 @@ import ( ...@@ -17,8 +16,8 @@ import (
*/ */
type BridgeMessage struct { type BridgeMessage struct {
Nonce U256 `gorm:"primaryKey"` MessageHash common.Hash `gorm:"primaryKey;serializer:json"`
MessageHash common.Hash `gorm:"serializer:json"` Nonce U256
SentMessageEventGUID uuid.UUID SentMessageEventGUID uuid.UUID
RelayedMessageEventGUID *uuid.UUID RelayedMessageEventGUID *uuid.UUID
...@@ -38,22 +37,20 @@ type L2BridgeMessage struct { ...@@ -38,22 +37,20 @@ type L2BridgeMessage struct {
} }
type BridgeMessagesView interface { type BridgeMessagesView interface {
L1BridgeMessage(*big.Int) (*L1BridgeMessage, error) L1BridgeMessage(common.Hash) (*L1BridgeMessage, error)
L1BridgeMessageByHash(common.Hash) (*L1BridgeMessage, error) L1BridgeMessageWithFilter(BridgeMessage) (*L1BridgeMessage, error)
LatestL1BridgeMessageNonce() (*big.Int, error)
L2BridgeMessage(*big.Int) (*L2BridgeMessage, error) L2BridgeMessage(common.Hash) (*L2BridgeMessage, error)
L2BridgeMessageByHash(common.Hash) (*L2BridgeMessage, error) L2BridgeMessageWithFilter(BridgeMessage) (*L2BridgeMessage, error)
LatestL2BridgeMessageNonce() (*big.Int, error)
} }
type BridgeMessagesDB interface { type BridgeMessagesDB interface {
BridgeMessagesView BridgeMessagesView
StoreL1BridgeMessages([]*L1BridgeMessage) error StoreL1BridgeMessages([]L1BridgeMessage) error
MarkRelayedL1BridgeMessage(common.Hash, uuid.UUID) error MarkRelayedL1BridgeMessage(common.Hash, uuid.UUID) error
StoreL2BridgeMessages([]*L2BridgeMessage) error StoreL2BridgeMessages([]L2BridgeMessage) error
MarkRelayedL2BridgeMessage(common.Hash, uuid.UUID) error MarkRelayedL2BridgeMessage(common.Hash, uuid.UUID) error
} }
...@@ -73,27 +70,18 @@ func newBridgeMessagesDB(db *gorm.DB) BridgeMessagesDB { ...@@ -73,27 +70,18 @@ func newBridgeMessagesDB(db *gorm.DB) BridgeMessagesDB {
* Arbitrary Messages Sent from L1 * Arbitrary Messages Sent from L1
*/ */
func (db bridgeMessagesDB) StoreL1BridgeMessages(messages []*L1BridgeMessage) error { func (db bridgeMessagesDB) StoreL1BridgeMessages(messages []L1BridgeMessage) error {
result := db.gorm.Create(&messages) result := db.gorm.Create(&messages)
return result.Error return result.Error
} }
func (db bridgeMessagesDB) L1BridgeMessage(nonce *big.Int) (*L1BridgeMessage, error) { func (db bridgeMessagesDB) L1BridgeMessage(msgHash common.Hash) (*L1BridgeMessage, error) {
var sentMessage L1BridgeMessage return db.L1BridgeMessageWithFilter(BridgeMessage{MessageHash: msgHash})
result := db.gorm.Where(&BridgeMessage{Nonce: U256{Int: nonce}}).Take(&sentMessage)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &sentMessage, nil
} }
func (db bridgeMessagesDB) L1BridgeMessageByHash(messageHash common.Hash) (*L1BridgeMessage, error) { func (db bridgeMessagesDB) L1BridgeMessageWithFilter(filter BridgeMessage) (*L1BridgeMessage, error) {
var sentMessage L1BridgeMessage var sentMessage L1BridgeMessage
result := db.gorm.Where(&BridgeMessage{MessageHash: messageHash}).Take(&sentMessage) result := db.gorm.Where(&filter).Take(&sentMessage)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -104,25 +92,8 @@ func (db bridgeMessagesDB) L1BridgeMessageByHash(messageHash common.Hash) (*L1Br ...@@ -104,25 +92,8 @@ func (db bridgeMessagesDB) L1BridgeMessageByHash(messageHash common.Hash) (*L1Br
return &sentMessage, nil return &sentMessage, nil
} }
func (db bridgeMessagesDB) LatestL1BridgeMessageNonce() (*big.Int, error) {
var sentMessage L1BridgeMessage
result := db.gorm.Order("nonce DESC").Take(&sentMessage)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return sentMessage.Nonce.Int, nil
}
/**
* Arbitrary Messages Sent from L2
*/
func (db bridgeMessagesDB) MarkRelayedL1BridgeMessage(messageHash common.Hash, relayEvent uuid.UUID) error { func (db bridgeMessagesDB) MarkRelayedL1BridgeMessage(messageHash common.Hash, relayEvent uuid.UUID) error {
message, err := db.L1BridgeMessageByHash(messageHash) message, err := db.L1BridgeMessage(messageHash)
if err != nil { if err != nil {
return err return err
} else if message == nil { } else if message == nil {
...@@ -134,27 +105,22 @@ func (db bridgeMessagesDB) MarkRelayedL1BridgeMessage(messageHash common.Hash, r ...@@ -134,27 +105,22 @@ func (db bridgeMessagesDB) MarkRelayedL1BridgeMessage(messageHash common.Hash, r
return result.Error return result.Error
} }
func (db bridgeMessagesDB) StoreL2BridgeMessages(messages []*L2BridgeMessage) error { /**
* Arbitrary Messages Sent from L2
*/
func (db bridgeMessagesDB) StoreL2BridgeMessages(messages []L2BridgeMessage) error {
result := db.gorm.Create(&messages) result := db.gorm.Create(&messages)
return result.Error return result.Error
} }
func (db bridgeMessagesDB) L2BridgeMessage(nonce *big.Int) (*L2BridgeMessage, error) { func (db bridgeMessagesDB) L2BridgeMessage(msgHash common.Hash) (*L2BridgeMessage, error) {
var sentMessage L2BridgeMessage return db.L2BridgeMessageWithFilter(BridgeMessage{MessageHash: msgHash})
result := db.gorm.Where(&BridgeMessage{Nonce: U256{Int: nonce}}).Take(&sentMessage)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &sentMessage, nil
} }
func (db bridgeMessagesDB) L2BridgeMessageByHash(messageHash common.Hash) (*L2BridgeMessage, error) { func (db bridgeMessagesDB) L2BridgeMessageWithFilter(filter BridgeMessage) (*L2BridgeMessage, error) {
var sentMessage L2BridgeMessage var sentMessage L2BridgeMessage
result := db.gorm.Where(&BridgeMessage{MessageHash: messageHash}).Take(&sentMessage) result := db.gorm.Where(&filter).Take(&sentMessage)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
...@@ -165,21 +131,8 @@ func (db bridgeMessagesDB) L2BridgeMessageByHash(messageHash common.Hash) (*L2Br ...@@ -165,21 +131,8 @@ func (db bridgeMessagesDB) L2BridgeMessageByHash(messageHash common.Hash) (*L2Br
return &sentMessage, nil return &sentMessage, nil
} }
func (db bridgeMessagesDB) LatestL2BridgeMessageNonce() (*big.Int, error) {
var sentMessage L2BridgeMessage
result := db.gorm.Order("nonce DESC").Take(&sentMessage)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return sentMessage.Nonce.Int, nil
}
func (db bridgeMessagesDB) MarkRelayedL2BridgeMessage(messageHash common.Hash, relayEvent uuid.UUID) error { func (db bridgeMessagesDB) MarkRelayedL2BridgeMessage(messageHash common.Hash, relayEvent uuid.UUID) error {
message, err := db.L2BridgeMessageByHash(messageHash) message, err := db.L2BridgeMessage(messageHash)
if err != nil { if err != nil {
return err return err
} else if message == nil { } else if message == nil {
......
...@@ -24,21 +24,16 @@ type Transaction struct { ...@@ -24,21 +24,16 @@ type Transaction struct {
} }
type L1TransactionDeposit struct { type L1TransactionDeposit struct {
SourceHash common.Hash `gorm:"serializer:json;primaryKey"` SourceHash common.Hash `gorm:"serializer:json;primaryKey"`
L2TransactionHash common.Hash `gorm:"serializer:json"` L2TransactionHash common.Hash `gorm:"serializer:json"`
InitiatedL1EventGUID uuid.UUID InitiatedL1EventGUID uuid.UUID
Version U256
OpaqueData hexutil.Bytes `gorm:"serializer:json"`
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
GasLimit U256 GasLimit U256
} }
type L2TransactionWithdrawal struct { type L2TransactionWithdrawal struct {
WithdrawalHash common.Hash `gorm:"serializer:json;primaryKey"` WithdrawalHash common.Hash `gorm:"serializer:json;primaryKey"`
Nonce U256 Nonce U256
InitiatedL2EventGUID uuid.UUID InitiatedL2EventGUID uuid.UUID
...@@ -58,9 +53,9 @@ type BridgeTransactionsView interface { ...@@ -58,9 +53,9 @@ type BridgeTransactionsView interface {
type BridgeTransactionsDB interface { type BridgeTransactionsDB interface {
BridgeTransactionsView BridgeTransactionsView
StoreL1TransactionDeposits([]*L1TransactionDeposit) error StoreL1TransactionDeposits([]L1TransactionDeposit) error
StoreL2TransactionWithdrawals([]*L2TransactionWithdrawal) error StoreL2TransactionWithdrawals([]L2TransactionWithdrawal) error
MarkL2TransactionWithdrawalProvenEvent(common.Hash, uuid.UUID) error MarkL2TransactionWithdrawalProvenEvent(common.Hash, uuid.UUID) error
MarkL2TransactionWithdrawalFinalizedEvent(common.Hash, uuid.UUID, bool) error MarkL2TransactionWithdrawalFinalizedEvent(common.Hash, uuid.UUID, bool) error
} }
...@@ -81,7 +76,7 @@ func newBridgeTransactionsDB(db *gorm.DB) BridgeTransactionsDB { ...@@ -81,7 +76,7 @@ func newBridgeTransactionsDB(db *gorm.DB) BridgeTransactionsDB {
* Transactions deposited from L1 * Transactions deposited from L1
*/ */
func (db *bridgeTransactionsDB) StoreL1TransactionDeposits(deposits []*L1TransactionDeposit) error { func (db *bridgeTransactionsDB) StoreL1TransactionDeposits(deposits []L1TransactionDeposit) error {
result := db.gorm.Create(&deposits) result := db.gorm.Create(&deposits)
return result.Error return result.Error
} }
...@@ -103,7 +98,7 @@ func (db *bridgeTransactionsDB) L1TransactionDeposit(sourceHash common.Hash) (*L ...@@ -103,7 +98,7 @@ func (db *bridgeTransactionsDB) L1TransactionDeposit(sourceHash common.Hash) (*L
* Transactions withdrawn from L2 * Transactions withdrawn from L2
*/ */
func (db *bridgeTransactionsDB) StoreL2TransactionWithdrawals(withdrawals []*L2TransactionWithdrawal) error { func (db *bridgeTransactionsDB) StoreL2TransactionWithdrawals(withdrawals []L2TransactionWithdrawal) error {
result := db.gorm.Create(&withdrawals) result := db.gorm.Create(&withdrawals)
return result.Error return result.Error
} }
......
This diff is collapsed.
...@@ -2,6 +2,7 @@ package database ...@@ -2,6 +2,7 @@ package database
import ( import (
"errors" "errors"
"fmt"
"math/big" "math/big"
"gorm.io/gorm" "gorm.io/gorm"
...@@ -74,19 +75,23 @@ type L2ContractEvent struct { ...@@ -74,19 +75,23 @@ type L2ContractEvent struct {
type ContractEventsView interface { type ContractEventsView interface {
L1ContractEvent(uuid.UUID) (*L1ContractEvent, error) L1ContractEvent(uuid.UUID) (*L1ContractEvent, error)
L1ContractEventByTxLogIndex(common.Hash, uint64) (*L1ContractEvent, error) L1ContractEventWithFilter(ContractEvent) (*L1ContractEvent, error)
L1ContractEventsWithFilter(ContractEvent, *big.Int, *big.Int) ([]L1ContractEvent, error) L1ContractEventsWithFilter(ContractEvent, *big.Int, *big.Int) ([]L1ContractEvent, error)
L1LatestContractEventWithFilter(ContractEvent) (*L1ContractEvent, error)
L2ContractEvent(uuid.UUID) (*L2ContractEvent, error) L2ContractEvent(uuid.UUID) (*L2ContractEvent, error)
L2ContractEventByTxLogIndex(common.Hash, uint64) (*L2ContractEvent, error) L2ContractEventWithFilter(ContractEvent) (*L2ContractEvent, error)
L2ContractEventsWithFilter(ContractEvent, *big.Int, *big.Int) ([]L2ContractEvent, error) L2ContractEventsWithFilter(ContractEvent, *big.Int, *big.Int) ([]L2ContractEvent, error)
L2LatestContractEventWithFilter(ContractEvent) (*L2ContractEvent, error)
ContractEventsWithFilter(ContractEvent, string, *big.Int, *big.Int) ([]ContractEvent, error)
} }
type ContractEventsDB interface { type ContractEventsDB interface {
ContractEventsView ContractEventsView
StoreL1ContractEvents([]*L1ContractEvent) error StoreL1ContractEvents([]L1ContractEvent) error
StoreL2ContractEvents([]*L2ContractEvent) error StoreL2ContractEvents([]L2ContractEvent) error
} }
/** /**
...@@ -103,33 +108,22 @@ func newContractEventsDB(db *gorm.DB) ContractEventsDB { ...@@ -103,33 +108,22 @@ func newContractEventsDB(db *gorm.DB) ContractEventsDB {
// L1 // L1
func (db *contractEventsDB) StoreL1ContractEvents(events []*L1ContractEvent) error { func (db *contractEventsDB) StoreL1ContractEvents(events []L1ContractEvent) error {
result := db.gorm.Create(&events) result := db.gorm.Create(&events)
return result.Error return result.Error
} }
func (db *contractEventsDB) L1ContractEvent(uuid uuid.UUID) (*L1ContractEvent, error) { func (db *contractEventsDB) L1ContractEvent(uuid uuid.UUID) (*L1ContractEvent, error) {
var l1ContractEvent L1ContractEvent return db.L1ContractEventWithFilter(ContractEvent{GUID: uuid})
result := db.gorm.Where(&ContractEvent{GUID: uuid}).Take(&l1ContractEvent)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &l1ContractEvent, nil
} }
func (db *contractEventsDB) L1ContractEventByTxLogIndex(txHash common.Hash, logIndex uint64) (*L1ContractEvent, error) { func (db *contractEventsDB) L1ContractEventWithFilter(filter ContractEvent) (*L1ContractEvent, error) {
var l1ContractEvent L1ContractEvent var l1ContractEvent L1ContractEvent
result := db.gorm.Where(&ContractEvent{TransactionHash: txHash, LogIndex: logIndex}).Take(&l1ContractEvent) result := db.gorm.Where(&filter).Take(&l1ContractEvent)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
} }
return nil, result.Error return nil, result.Error
} }
...@@ -140,6 +134,12 @@ func (db *contractEventsDB) L1ContractEventsWithFilter(filter ContractEvent, fro ...@@ -140,6 +134,12 @@ func (db *contractEventsDB) L1ContractEventsWithFilter(filter ContractEvent, fro
if fromHeight == nil { if fromHeight == nil {
fromHeight = big.NewInt(0) fromHeight = big.NewInt(0)
} }
if toHeight == nil {
return nil, errors.New("end height unspecified")
}
if fromHeight.Cmp(toHeight) > 0 {
return nil, fmt.Errorf("fromHeight %d is greater than toHeight %d", fromHeight, toHeight)
}
query := db.gorm.Table("l1_contract_events").Where(&filter) query := db.gorm.Table("l1_contract_events").Where(&filter)
query = query.Joins("INNER JOIN l1_block_headers ON l1_contract_events.block_hash = l1_block_headers.hash") query = query.Joins("INNER JOIN l1_block_headers ON l1_contract_events.block_hash = l1_block_headers.hash")
...@@ -160,35 +160,37 @@ func (db *contractEventsDB) L1ContractEventsWithFilter(filter ContractEvent, fro ...@@ -160,35 +160,37 @@ func (db *contractEventsDB) L1ContractEventsWithFilter(filter ContractEvent, fro
return events, nil return events, nil
} }
// L2 func (db *contractEventsDB) L1LatestContractEventWithFilter(filter ContractEvent) (*L1ContractEvent, error) {
var l1ContractEvent L1ContractEvent
func (db *contractEventsDB) StoreL2ContractEvents(events []*L2ContractEvent) error { result := db.gorm.Where(&filter).Order("timestamp DESC").Take(&l1ContractEvent)
result := db.gorm.Create(&events)
return result.Error
}
func (db *contractEventsDB) L2ContractEvent(uuid uuid.UUID) (*L2ContractEvent, error) {
var l2ContractEvent L2ContractEvent
result := db.gorm.Where(&ContractEvent{GUID: uuid}).Take(&l2ContractEvent)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
} }
return nil, result.Error return nil, result.Error
} }
return &l2ContractEvent, nil return &l1ContractEvent, nil
} }
func (db *contractEventsDB) L2ContractEventByTxLogIndex(txHash common.Hash, logIndex uint64) (*L2ContractEvent, error) { // L2
func (db *contractEventsDB) StoreL2ContractEvents(events []L2ContractEvent) error {
result := db.gorm.Create(&events)
return result.Error
}
func (db *contractEventsDB) L2ContractEvent(uuid uuid.UUID) (*L2ContractEvent, error) {
return db.L2ContractEventWithFilter(ContractEvent{GUID: uuid})
}
func (db *contractEventsDB) L2ContractEventWithFilter(filter ContractEvent) (*L2ContractEvent, error) {
var l2ContractEvent L2ContractEvent var l2ContractEvent L2ContractEvent
result := db.gorm.Where(&ContractEvent{TransactionHash: txHash, LogIndex: logIndex}).Take(&l2ContractEvent) result := db.gorm.Where(&filter).Take(&l2ContractEvent)
if result.Error != nil { if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil return nil, nil
} }
return nil, result.Error return nil, result.Error
} }
...@@ -199,6 +201,12 @@ func (db *contractEventsDB) L2ContractEventsWithFilter(filter ContractEvent, fro ...@@ -199,6 +201,12 @@ func (db *contractEventsDB) L2ContractEventsWithFilter(filter ContractEvent, fro
if fromHeight == nil { if fromHeight == nil {
fromHeight = big.NewInt(0) fromHeight = big.NewInt(0)
} }
if toHeight == nil {
return nil, errors.New("end height unspecified")
}
if fromHeight.Cmp(toHeight) > 0 {
return nil, fmt.Errorf("fromHeight %d is greater than toHeight %d", fromHeight, toHeight)
}
query := db.gorm.Table("l2_contract_events").Where(&filter) query := db.gorm.Table("l2_contract_events").Where(&filter)
query = query.Joins("INNER JOIN l2_block_headers ON l2_contract_events.block_hash = l2_block_headers.hash") query = query.Joins("INNER JOIN l2_block_headers ON l2_contract_events.block_hash = l2_block_headers.hash")
...@@ -218,3 +226,46 @@ func (db *contractEventsDB) L2ContractEventsWithFilter(filter ContractEvent, fro ...@@ -218,3 +226,46 @@ func (db *contractEventsDB) L2ContractEventsWithFilter(filter ContractEvent, fro
return events, nil return events, nil
} }
func (db *contractEventsDB) L2LatestContractEventWithFilter(filter ContractEvent) (*L2ContractEvent, error) {
var l2ContractEvent L2ContractEvent
result := db.gorm.Where(&filter).Order("timestamp DESC").Take(&l2ContractEvent)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &l2ContractEvent, nil
}
// Auxiliary methods for both L1 and L2
// ContractEventsWithFilter will retrieve contract events within the specified range according to the `chainSelector`.
func (db *contractEventsDB) ContractEventsWithFilter(filter ContractEvent, chainSelector string, fromHeight, toHeight *big.Int) ([]ContractEvent, error) {
switch chainSelector {
case "l1":
l1Events, err := db.L1ContractEventsWithFilter(filter, fromHeight, toHeight)
if err != nil {
return nil, err
}
events := make([]ContractEvent, len(l1Events))
for i := range l1Events {
events[i] = l1Events[i].ContractEvent
}
return events, nil
case "l2":
l2Events, err := db.L2ContractEventsWithFilter(filter, fromHeight, toHeight)
if err != nil {
return nil, err
}
events := make([]ContractEvent, len(l2Events))
for i := range l2Events {
events[i] = l2Events[i].ContractEvent
}
return events, nil
default:
return nil, errors.New("expected 'l1' or 'l2' for chain selection")
}
}
...@@ -32,9 +32,6 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) { ...@@ -32,9 +32,6 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
l1Opts.Value = big.NewInt(params.Ether) l1Opts.Value = big.NewInt(params.Ether)
// Pause the processor to track relayed event
testSuite.Indexer.L2Processor.PauseForTest()
// (1) Send the Message // (1) Send the Message
sentMsgTx, err := l1CrossDomainMessenger.SendMessage(l1Opts, aliceAddr, calldata, 100_000) sentMsgTx, err := l1CrossDomainMessenger.SendMessage(l1Opts, aliceAddr, calldata, 100_000)
require.NoError(t, err) require.NoError(t, err)
...@@ -46,7 +43,7 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) { ...@@ -46,7 +43,7 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
// wait for processor catchup // wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
return l1Header != nil && l1Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil
})) }))
...@@ -57,12 +54,12 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) { ...@@ -57,12 +54,12 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
nonceBytes := [31]byte{0: byte(1)} nonceBytes := [31]byte{0: byte(1)}
nonce := new(big.Int).SetBytes(nonceBytes[:]) nonce := new(big.Int).SetBytes(nonceBytes[:])
sentMessage, err := testSuite.DB.BridgeMessages.L1BridgeMessage(nonce) sentMessage, err := testSuite.DB.BridgeMessages.L1BridgeMessage(parsedMessage.MessageHash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, sentMessage) require.NotNil(t, sentMessage)
require.NotNil(t, sentMessage.SentMessageEventGUID) require.NotNil(t, sentMessage.SentMessageEventGUID)
require.Equal(t, depositInfo.DepositTx.SourceHash, sentMessage.TransactionSourceHash) require.Equal(t, depositInfo.DepositTx.SourceHash, sentMessage.TransactionSourceHash)
require.Equal(t, parsedMessage.MessageHash, sentMessage.MessageHash) require.Equal(t, nonce.Uint64(), sentMessage.Nonce.Int.Uint64())
require.Equal(t, uint64(100_000), sentMessage.GasLimit.Int.Uint64()) require.Equal(t, uint64(100_000), sentMessage.GasLimit.Int.Uint64())
require.Equal(t, big.NewInt(params.Ether), sentMessage.Tx.Amount.Int) require.Equal(t, big.NewInt(params.Ether), sentMessage.Tx.Amount.Int)
require.Equal(t, aliceAddr, sentMessage.Tx.FromAddress) require.Equal(t, aliceAddr, sentMessage.Tx.FromAddress)
...@@ -70,20 +67,21 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) { ...@@ -70,20 +67,21 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
require.ElementsMatch(t, calldata, sentMessage.Tx.Data) require.ElementsMatch(t, calldata, sentMessage.Tx.Data)
// (2) Process RelayedMesssage on inclusion // (2) Process RelayedMesssage on inclusion
require.Nil(t, sentMessage.RelayedMessageEventGUID) // - We dont assert that `RelayedMessageEventGUID` is nil prior to inclusion since there isn't a
testSuite.Indexer.L2Processor.ResumeForTest() // a straightforward way of pausing/resuming the processors at the right time. The codepath is the
// same for L2->L1 messages which does check for this so we are still covered
transaction, err := testSuite.DB.BridgeTransactions.L1TransactionDeposit(sentMessage.TransactionSourceHash) transaction, err := testSuite.DB.BridgeTransactions.L1TransactionDeposit(sentMessage.TransactionSourceHash)
require.NoError(t, err) require.NoError(t, err)
// wait for processor catchup // wait for processor catchup
depositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, transaction.L2TransactionHash) l2DepositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, transaction.L2TransactionHash)
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader() l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
return l2Header != nil && l2Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil return l2Header != nil && l2Header.Number.Uint64() >= l2DepositReceipt.BlockNumber.Uint64(), nil
})) }))
sentMessage, err = testSuite.DB.BridgeMessages.L1BridgeMessage(nonce) sentMessage, err = testSuite.DB.BridgeMessages.L1BridgeMessage(parsedMessage.MessageHash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, sentMessage) require.NotNil(t, sentMessage)
require.NotNil(t, sentMessage.RelayedMessageEventGUID) require.NotNil(t, sentMessage.RelayedMessageEventGUID)
...@@ -132,7 +130,7 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) { ...@@ -132,7 +130,7 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
// wait for processor catchup // wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader() l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
return l2Header != nil && l2Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil return l2Header != nil && l2Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil
})) }))
...@@ -143,12 +141,12 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) { ...@@ -143,12 +141,12 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
nonceBytes := [31]byte{0: byte(1)} nonceBytes := [31]byte{0: byte(1)}
nonce := new(big.Int).SetBytes(nonceBytes[:]) nonce := new(big.Int).SetBytes(nonceBytes[:])
sentMessage, err := testSuite.DB.BridgeMessages.L2BridgeMessage(nonce) sentMessage, err := testSuite.DB.BridgeMessages.L2BridgeMessage(parsedMessage.MessageHash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, sentMessage) require.NotNil(t, sentMessage)
require.NotNil(t, sentMessage.SentMessageEventGUID) require.NotNil(t, sentMessage.SentMessageEventGUID)
require.Equal(t, withdrawalHash, sentMessage.TransactionWithdrawalHash) require.Equal(t, withdrawalHash, sentMessage.TransactionWithdrawalHash)
require.Equal(t, parsedMessage.MessageHash, sentMessage.MessageHash) require.Equal(t, nonce.Uint64(), sentMessage.Nonce.Int.Uint64())
require.Equal(t, uint64(100_000), sentMessage.GasLimit.Int.Uint64()) require.Equal(t, uint64(100_000), sentMessage.GasLimit.Int.Uint64())
require.Equal(t, big.NewInt(params.Ether), sentMessage.Tx.Amount.Int) require.Equal(t, big.NewInt(params.Ether), sentMessage.Tx.Amount.Int)
require.Equal(t, aliceAddr, sentMessage.Tx.FromAddress) require.Equal(t, aliceAddr, sentMessage.Tx.FromAddress)
...@@ -157,16 +155,16 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) { ...@@ -157,16 +155,16 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
// (2) Process RelayedMessage on withdrawal finalization // (2) Process RelayedMessage on withdrawal finalization
require.Nil(t, sentMessage.RelayedMessageEventGUID) require.Nil(t, sentMessage.RelayedMessageEventGUID)
_, finalizedReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, sentMsgReceipt) _, finalizedReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, sentMsgReceipt)
// wait for processor catchup // wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizedReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= finalizedReceipt.BlockNumber.Uint64(), nil
})) }))
// message is marked as relayed // message is marked as relayed
sentMessage, err = testSuite.DB.BridgeMessages.L2BridgeMessage(nonce) sentMessage, err = testSuite.DB.BridgeMessages.L2BridgeMessage(parsedMessage.MessageHash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, sentMessage) require.NotNil(t, sentMessage)
require.NotNil(t, sentMessage.RelayedMessageEventGUID) require.NotNil(t, sentMessage.RelayedMessageEventGUID)
......
...@@ -46,12 +46,13 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) { ...@@ -46,12 +46,13 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) {
// wait for processor catchup // wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
})) }))
deposit, err := testSuite.DB.BridgeTransactions.L1TransactionDeposit(depositInfo.DepositTx.SourceHash) deposit, err := testSuite.DB.BridgeTransactions.L1TransactionDeposit(depositInfo.DepositTx.SourceHash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, deposit)
require.Equal(t, depositL2TxHash, deposit.L2TransactionHash) require.Equal(t, depositL2TxHash, deposit.L2TransactionHash)
require.Equal(t, big.NewInt(100_000), deposit.GasLimit.Int) require.Equal(t, big.NewInt(100_000), deposit.GasLimit.Int)
require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int) require.Equal(t, big.NewInt(params.Ether), deposit.Tx.Amount.Int)
...@@ -59,9 +60,6 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) { ...@@ -59,9 +60,6 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) {
require.Equal(t, aliceAddr, deposit.Tx.ToAddress) require.Equal(t, aliceAddr, deposit.Tx.ToAddress)
require.ElementsMatch(t, calldata, deposit.Tx.Data) require.ElementsMatch(t, calldata, deposit.Tx.Data)
require.Equal(t, depositInfo.Version.Uint64(), deposit.Version.Int.Uint64())
require.ElementsMatch(t, depositInfo.OpaqueData, deposit.OpaqueData)
event, err := testSuite.DB.ContractEvents.L1ContractEvent(deposit.InitiatedL1EventGUID) event, err := testSuite.DB.ContractEvents.L1ContractEvent(deposit.InitiatedL1EventGUID)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, event) require.NotNil(t, event)
...@@ -103,7 +101,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) { ...@@ -103,7 +101,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
// wait for processor catchup // wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader() l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
})) }))
...@@ -114,6 +112,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) { ...@@ -114,6 +112,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
withdraw, err := testSuite.DB.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash) withdraw, err := testSuite.DB.BridgeTransactions.L2TransactionWithdrawal(withdrawalHash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, withdraw)
require.Equal(t, msgPassed.Nonce.Uint64(), withdraw.Nonce.Int.Uint64()) require.Equal(t, msgPassed.Nonce.Uint64(), withdraw.Nonce.Int.Uint64())
require.Equal(t, big.NewInt(100_000), withdraw.GasLimit.Int) require.Equal(t, big.NewInt(100_000), withdraw.GasLimit.Int)
require.Equal(t, big.NewInt(params.Ether), withdraw.Tx.Amount.Int) require.Equal(t, big.NewInt(params.Ether), withdraw.Tx.Amount.Int)
...@@ -130,9 +129,9 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) { ...@@ -130,9 +129,9 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
require.Nil(t, withdraw.ProvenL1EventGUID) require.Nil(t, withdraw.ProvenL1EventGUID)
require.Nil(t, withdraw.FinalizedL1EventGUID) require.Nil(t, withdraw.FinalizedL1EventGUID)
withdrawParams, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt) withdrawParams, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
return l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64(), nil
})) }))
...@@ -150,7 +149,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) { ...@@ -150,7 +149,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpCfg.Secrets.Alice, proveReceipt, withdrawParams) finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpCfg.Secrets.Alice, proveReceipt, withdrawParams)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
})) }))
...@@ -190,9 +189,9 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserFailedWithdrawal(t *testing.T) ...@@ -190,9 +189,9 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserFailedWithdrawal(t *testing.T)
require.NoError(t, err) require.NoError(t, err)
// Prove&Finalize withdrawal // Prove&Finalize withdrawal
_, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.Nodes["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt) _, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
})) }))
......
...@@ -6,43 +6,45 @@ import ( ...@@ -6,43 +6,45 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestE2EBlockHeaders(t *testing.T) { func TestE2EETL(t *testing.T) {
testSuite := createE2ETestSuite(t) testSuite := createE2ETestSuite(t)
l2OutputOracle, err := bindings.NewL2OutputOracle(testSuite.OpCfg.L1Deployments.L2OutputOracleProxy, testSuite.L1Client) l2OutputOracle, err := bindings.NewL2OutputOracle(testSuite.OpCfg.L1Deployments.L2OutputOracleProxy, testSuite.L1Client)
require.NoError(t, err) require.NoError(t, err)
// wait for at least 10 L2 blocks to be created & posted on L1 // wait for at least 10 L2 blocks posted on L1
require.NoError(t, wait.For(context.Background(), time.Second, func() (bool, error) { require.NoError(t, wait.For(context.Background(), time.Second, func() (bool, error) {
l2Height, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{Context: context.Background()}) l2Height, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{Context: context.Background()})
return l2Height != nil && l2Height.Uint64() >= 9, err return l2Height != nil && l2Height.Uint64() >= 9, err
})) }))
// ensure the processors are caught up to this state // ensure we've indexed up to this state
l1Height, err := testSuite.L1Client.BlockNumber(context.Background()) l1Height, err := testSuite.L1Client.BlockNumber(context.Background())
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, wait.For(context.Background(), time.Second, func() (bool, error) { require.NoError(t, wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.L1Processor.LatestProcessedHeader() l1Header, err := testSuite.DB.Blocks.L1LatestBlockHeader()
l2Header := testSuite.Indexer.L2Processor.LatestProcessedHeader() require.NoError(t, err)
return (l1Header != nil && l1Header.Number.Uint64() >= l1Height) && (l2Header != nil && l2Header.Number.Uint64() >= 9), nil
l2Header, err := testSuite.DB.Blocks.L2LatestBlockHeader()
require.NoError(t, err)
return (l1Header != nil && l1Header.Number.Int.Uint64() >= l1Height) && (l2Header != nil && l2Header.Number.Int.Uint64() >= 9), nil
})) }))
t.Run("indexes L2 blocks", func(t *testing.T) { t.Run("indexes all L2 blocks", func(t *testing.T) {
latestL2Header, err := testSuite.DB.Blocks.LatestL2BlockHeader() latestL2Header, err := testSuite.DB.Blocks.L2LatestBlockHeader()
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, latestL2Header) require.NotNil(t, latestL2Header)
require.True(t, latestL2Header.Number.Int.Uint64() >= 9) require.True(t, latestL2Header.Number.Int.Uint64() >= 9)
...@@ -50,7 +52,7 @@ func TestE2EBlockHeaders(t *testing.T) { ...@@ -50,7 +52,7 @@ func TestE2EBlockHeaders(t *testing.T) {
for i := int64(0); i < 10; i++ { for i := int64(0); i < 10; i++ {
height := big.NewInt(i) height := big.NewInt(i)
indexedHeader, err := testSuite.DB.Blocks.L2BlockHeader(height) indexedHeader, err := testSuite.DB.Blocks.L2BlockHeaderWithFilter(database.BlockHeader{Number: database.U256{Int: height}})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, indexedHeader) require.NotNil(t, indexedHeader)
...@@ -63,59 +65,61 @@ func TestE2EBlockHeaders(t *testing.T) { ...@@ -63,59 +65,61 @@ func TestE2EBlockHeaders(t *testing.T) {
require.Equal(t, header.ParentHash, indexedHeader.ParentHash) require.Equal(t, header.ParentHash, indexedHeader.ParentHash)
require.Equal(t, header.Time, indexedHeader.Timestamp) require.Equal(t, header.Time, indexedHeader.Timestamp)
// ensure the right rlp encoding is stored. checking the hashes sufficies // ensure the right rlp encoding is stored. checking the hashes
// suffices as it is based on the rlp bytes of the header
require.Equal(t, header.Hash(), indexedHeader.RLPHeader.Hash()) require.Equal(t, header.Hash(), indexedHeader.RLPHeader.Hash())
} }
}) })
t.Run("indexes L2 checkpoints", func(t *testing.T) { /*
latestOutput, err := testSuite.DB.Blocks.LatestCheckpointedOutput() TODO: ADD THIS BACK IN WHEN THESE MARKERS ARE INDEXED
require.NoError(t, err) t.Run("indexes L2 checkpoints", func(t *testing.T) {
require.NotNil(t, latestOutput) latestOutput, err := testSuite.DB.Blocks.LatestCheckpointedOutput()
require.GreaterOrEqual(t, latestOutput.L2BlockNumber.Int.Uint64(), uint64(9)) require.NoError(t, err)
require.NotNil(t, latestOutput)
l2EthClient, err := node.DialEthClient(testSuite.OpSys.Nodes["sequencer"].HTTPEndpoint()) require.GreaterOrEqual(t, latestOutput.L2BlockNumber.Int.Uint64(), uint64(9))
require.NoError(t, err)
l2EthClient, err := node.DialEthClient(testSuite.OpSys.EthInstances["sequencer"].HTTPEndpoint())
submissionInterval := testSuite.OpCfg.DeployConfig.L2OutputOracleSubmissionInterval require.NoError(t, err)
numOutputs := latestOutput.L2BlockNumber.Int.Uint64() / submissionInterval
for i := int64(0); i < int64(numOutputs); i++ { submissionInterval := testSuite.OpCfg.DeployConfig.L2OutputOracleSubmissionInterval
blockNumber := big.NewInt((i + 1) * int64(submissionInterval)) numOutputs := latestOutput.L2BlockNumber.Int.Uint64() / submissionInterval
for i := int64(0); i < int64(numOutputs); i++ {
output, err := testSuite.DB.Blocks.OutputProposal(big.NewInt(i)) blockNumber := big.NewInt((i + 1) * int64(submissionInterval))
require.NoError(t, err)
require.NotNil(t, output) output, err := testSuite.DB.Blocks.OutputProposal(big.NewInt(i))
require.Equal(t, i, output.L2OutputIndex.Int.Int64()) require.NoError(t, err)
require.Equal(t, blockNumber, output.L2BlockNumber.Int) require.NotNil(t, output)
require.NotEmpty(t, output.L1ContractEventGUID) require.Equal(t, i, output.L2OutputIndex.Int.Int64())
require.Equal(t, blockNumber, output.L2BlockNumber.Int)
// we may as well check the integrity of the output root require.NotEmpty(t, output.L1ContractEventGUID)
l2Block, err := testSuite.L2Client.BlockByNumber(context.Background(), blockNumber)
require.NoError(t, err) // we may as well check the integrity of the output root
messagePasserStorageHash, err := l2EthClient.StorageHash(predeploys.L2ToL1MessagePasserAddr, blockNumber) l2Block, err := testSuite.L2Client.BlockByNumber(context.Background(), blockNumber)
require.NoError(t, err) require.NoError(t, err)
messagePasserStorageHash, err := l2EthClient.StorageHash(predeploys.L2ToL1MessagePasserAddr, blockNumber)
// construct and check output root require.NoError(t, err)
outputRootPreImage := [128]byte{} // 4 words (first 32 are zero for version 0)
copy(outputRootPreImage[32:64], l2Block.Root().Bytes()) // state root // construct and check output root
copy(outputRootPreImage[64:96], messagePasserStorageHash.Bytes()) // message passer storage root outputRootPreImage := [128]byte{} // 4 words (first 32 are zero for version 0)
copy(outputRootPreImage[96:128], l2Block.Hash().Bytes()) // block hash copy(outputRootPreImage[32:64], l2Block.Root().Bytes()) // state root
require.Equal(t, crypto.Keccak256Hash(outputRootPreImage[:]), output.OutputRoot) copy(outputRootPreImage[64:96], messagePasserStorageHash.Bytes()) // message passer storage root
} copy(outputRootPreImage[96:128], l2Block.Hash().Bytes()) // block hash
}) require.Equal(t, crypto.Keccak256Hash(outputRootPreImage[:]), output.OutputRoot)
}
t.Run("indexes L1 logs and associated blocks", func(t *testing.T) { })
testCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) */
defer cancel()
t.Run("indexes L1 blocks with accompanying contract event", func(t *testing.T) {
l1Contracts := []common.Address{} l1Contracts := []common.Address{}
testSuite.OpCfg.L1Deployments.ForEach(func(name string, addr common.Address) { l1Contracts = append(l1Contracts, addr) }) testSuite.OpCfg.L1Deployments.ForEach(func(name string, addr common.Address) { l1Contracts = append(l1Contracts, addr) })
logFilter := ethereum.FilterQuery{FromBlock: big.NewInt(0), ToBlock: big.NewInt(int64(l1Height)), Addresses: l1Contracts} logFilter := ethereum.FilterQuery{FromBlock: big.NewInt(0), ToBlock: big.NewInt(int64(l1Height)), Addresses: l1Contracts}
logs, err := testSuite.L1Client.FilterLogs(testCtx, logFilter) // []types.Log logs, err := testSuite.L1Client.FilterLogs(context.Background(), logFilter) // []types.Log
require.NoError(t, err) require.NoError(t, err)
for _, log := range logs { for i := range logs {
contractEvent, err := testSuite.DB.ContractEvents.L1ContractEventByTxLogIndex(log.TxHash, uint64(log.Index)) log := logs[i]
contractEvent, err := testSuite.DB.ContractEvents.L1ContractEventWithFilter(database.ContractEvent{TransactionHash: log.TxHash, LogIndex: uint64(log.Index)})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, log.Topics[0], contractEvent.EventSignature) require.Equal(t, log.Topics[0], contractEvent.EventSignature)
require.Equal(t, log.BlockHash, contractEvent.BlockHash) require.Equal(t, log.BlockHash, contractEvent.BlockHash)
...@@ -131,12 +135,12 @@ func TestE2EBlockHeaders(t *testing.T) { ...@@ -131,12 +135,12 @@ func TestE2EBlockHeaders(t *testing.T) {
require.ElementsMatch(t, logRlp, contractEventRlp) require.ElementsMatch(t, logRlp, contractEventRlp)
// ensure the block is also indexed // ensure the block is also indexed
block, err := testSuite.L1Client.BlockByNumber(testCtx, big.NewInt(int64(log.BlockNumber))) block, err := testSuite.L1Client.BlockByNumber(context.Background(), big.NewInt(int64(log.BlockNumber)))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, block.Time(), contractEvent.Timestamp) require.Equal(t, block.Time(), contractEvent.Timestamp)
require.Equal(t, block.Hash(), contractEvent.BlockHash)
l1BlockHeader, err := testSuite.DB.Blocks.L1BlockHeader(block.Number()) l1BlockHeader, err := testSuite.DB.Blocks.L1BlockHeader(block.Hash())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, block.Hash(), l1BlockHeader.Hash) require.Equal(t, block.Hash(), l1BlockHeader.Hash)
require.Equal(t, block.ParentHash(), l1BlockHeader.ParentHash) require.Equal(t, block.ParentHash(), l1BlockHeader.ParentHash)
......
...@@ -49,7 +49,8 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -49,7 +49,8 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
// Rollup System Configuration and Start // Rollup System Configuration and Start
opCfg := op_e2e.DefaultSystemConfig(t) opCfg := op_e2e.DefaultSystemConfig(t)
opSys, err := opCfg.Start() opCfg.DeployConfig.FinalizationPeriodSeconds = 2
opSys, err := opCfg.Start(t)
require.NoError(t, err) require.NoError(t, err)
// E2E tests can run on the order of magnitude of minutes. Once // E2E tests can run on the order of magnitude of minutes. Once
...@@ -66,28 +67,22 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite { ...@@ -66,28 +67,22 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
User: dbUser, User: dbUser,
}, },
RPCs: config.RPCsConfig{ RPCs: config.RPCsConfig{
L1RPC: opSys.Nodes["l1"].HTTPEndpoint(), L1RPC: opSys.EthInstances["l1"].HTTPEndpoint(),
L2RPC: opSys.Nodes["sequencer"].HTTPEndpoint(), L2RPC: opSys.EthInstances["sequencer"].HTTPEndpoint(),
}, },
Chain: config.ChainConfig{ Chain: config.ChainConfig{
L1Contracts: config.L1Contracts{ L1Contracts: config.L1Contracts{
OptimismPortal: opCfg.L1Deployments.OptimismPortalProxy, OptimismPortalProxy: opCfg.L1Deployments.OptimismPortalProxy,
L2OutputOracle: opCfg.L1Deployments.L2OutputOracleProxy, L2OutputOracleProxy: opCfg.L1Deployments.L2OutputOracleProxy,
L1CrossDomainMessenger: opCfg.L1Deployments.L1CrossDomainMessengerProxy, L1CrossDomainMessengerProxy: opCfg.L1Deployments.L1CrossDomainMessengerProxy,
L1StandardBridge: opCfg.L1Deployments.L1StandardBridgeProxy, L1StandardBridgeProxy: opCfg.L1Deployments.L1StandardBridgeProxy,
L1ERC721Bridge: opCfg.L1Deployments.L1ERC721BridgeProxy,
}, },
}, },
} }
db, err := database.NewDB(indexerCfg.DB) db, err := database.NewDB(indexerCfg.DB)
require.NoError(t, err) require.NoError(t, err)
indexer, err := indexer.NewIndexer( indexer, err := indexer.NewIndexer(logger, indexerCfg.Chain, indexerCfg.RPCs, db)
indexerCfg.Chain,
indexerCfg.RPCs,
db,
logger,
)
require.NoError(t, err) require.NoError(t, err)
indexerStoppedCh := make(chan interface{}, 1) indexerStoppedCh := make(chan interface{}, 1)
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/indexer/processor" "github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -57,5 +57,5 @@ func CrossDomainMessengerSentMessageHash(sentMessage *bindings.CrossDomainMessen ...@@ -57,5 +57,5 @@ func CrossDomainMessengerSentMessageHash(sentMessage *bindings.CrossDomainMessen
return common.Hash{}, err return common.Hash{}, err
} }
return processor.CrossDomainMessageHash(abi, sentMessage, value) return contracts.CrossDomainMessageHash(abi, sentMessage, value)
} }
package etl
import (
"context"
"errors"
"time"
"github.com/ethereum-optimism/optimism/indexer/node"
"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/log"
)
const (
defaultLoopInterval = 5 * time.Second
defaultHeaderBufferSize = 500
)
type ETL struct {
log log.Logger
headerTraversal *node.HeaderTraversal
ethClient *ethclient.Client
contracts []common.Address
etlBatches chan ETLBatch
}
type ETLBatch struct {
Logger log.Logger
Headers []types.Header
HeaderMap map[common.Hash]*types.Header
Logs []types.Log
HeadersWithLog map[common.Hash]bool
}
func (etl *ETL) Start(ctx context.Context) error {
done := ctx.Done()
pollTicker := time.NewTicker(defaultLoopInterval)
defer pollTicker.Stop()
etl.log.Info("starting etl...")
var headers []types.Header
for {
select {
case <-done:
etl.log.Info("stopping etl")
return nil
case <-pollTicker.C:
if len(headers) == 0 {
newHeaders, err := etl.headerTraversal.NextFinalizedHeaders(defaultHeaderBufferSize)
if err != nil {
etl.log.Error("error querying for headers", "err", err)
continue
}
if len(newHeaders) == 0 {
// Logged as an error since this loop should be operating at a longer interval than the provider
etl.log.Error("no new headers. processor unexpectedly at head...")
continue
}
headers = newHeaders
} else {
etl.log.Info("retrying previous batch")
}
firstHeader := headers[0]
lastHeader := headers[len(headers)-1]
batchLog := etl.log.New("batch_start_block_number", firstHeader.Number, "batch_end_block_number", lastHeader.Number)
batchLog.Info("extracting batch", "size", len(headers))
headerMap := make(map[common.Hash]*types.Header, len(headers))
for i := range headers {
headerMap[headers[i].Hash()] = &headers[i]
}
headersWithLog := make(map[common.Hash]bool, len(headers))
logFilter := ethereum.FilterQuery{FromBlock: firstHeader.Number, ToBlock: lastHeader.Number, Addresses: etl.contracts}
logs, err := etl.ethClient.FilterLogs(context.Background(), logFilter)
if err != nil {
batchLog.Info("unable to extract logs within batch", "err", err)
continue // spin and try again
}
for i := range logs {
if _, ok := headerMap[logs[i].BlockHash]; !ok {
// NOTE. Definitely an error state if the none of the headers were re-orged out in between
// the blocks and logs retreival operations. However, we need to gracefully handle reorgs
batchLog.Error("log found with block hash not in the batch", "block_hash", logs[i].BlockHash, "log_index", logs[i].Index)
return errors.New("parsed log with a block hash not in the fetched batch")
}
headersWithLog[logs[i].BlockHash] = true
}
if len(logs) > 0 {
batchLog.Info("detected logs", "size", len(logs))
}
// create a new reference such that subsequent changes to `headers` does not affect the reference
headersRef := headers
batch := ETLBatch{Logger: batchLog, Headers: headersRef, HeaderMap: headerMap, Logs: logs, HeadersWithLog: headersWithLog}
headers = nil
etl.etlBatches <- batch
}
}
}
package etl
import (
"context"
"errors"
"reflect"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type L1ETL struct {
ETL
db *database.DB
}
func NewL1ETL(log log.Logger, db *database.DB, client node.EthClient, contracts config.L1Contracts) (*L1ETL, error) {
log = log.New("etl", "l1")
contractValue := reflect.ValueOf(contracts)
fields := reflect.VisibleFields(reflect.TypeOf(contracts))
l1Contracts := make([]common.Address, len(fields))
for i, field := range fields {
// ruleid: unsafe-reflect-by-name
addr, ok := (contractValue.FieldByName(field.Name).Interface()).(common.Address)
if !ok {
log.Error("non-address found in L1Contracts", "name", field.Name)
return nil, errors.New("non-address found in L1Contracts")
}
log.Info("configured contract", "name", field.Name, "addr", addr)
l1Contracts[i] = addr
}
latestHeader, err := db.Blocks.L1LatestBlockHeader()
if err != nil {
return nil, err
}
var fromHeader *types.Header
if latestHeader != nil {
log.Info("detected last indexed block", "number", latestHeader.Number.Int, "hash", latestHeader.Hash)
fromHeader = latestHeader.RLPHeader.Header()
} else {
log.Info("no indexed state, starting from genesis")
}
etlBatches := make(chan ETLBatch)
etl := ETL{
log: log,
headerTraversal: node.NewHeaderTraversal(client, fromHeader),
ethClient: client.GethEthClient(),
contracts: l1Contracts,
etlBatches: etlBatches,
}
return &L1ETL{ETL: etl, db: db}, nil
}
func (l1Etl *L1ETL) Start(ctx context.Context) error {
errCh := make(chan error, 1)
go func() {
errCh <- l1Etl.ETL.Start(ctx)
}()
for {
select {
case err := <-errCh:
return err
// Index incoming batches
case batch := <-l1Etl.etlBatches:
// Pull out only L1 blocks that have emitted a log ( <= batch.Headers )
l1BlockHeaders := make([]database.L1BlockHeader, 0, len(batch.Headers))
for i := range batch.Headers {
if _, ok := batch.HeadersWithLog[batch.Headers[i].Hash()]; ok {
l1BlockHeaders = append(l1BlockHeaders, database.L1BlockHeader{BlockHeader: database.BlockHeaderFromHeader(&batch.Headers[i])})
}
}
if len(l1BlockHeaders) == 0 {
batch.Logger.Info("no l1 blocks with logs in batch")
continue
}
l1ContractEvents := make([]database.L1ContractEvent, len(batch.Logs))
for i := range batch.Logs {
timestamp := batch.HeaderMap[batch.Logs[i].BlockHash].Time
l1ContractEvents[i] = database.L1ContractEvent{ContractEvent: database.ContractEventFromLog(&batch.Logs[i], timestamp)}
}
// Continually try to persist this batch. If it fails after 10 attempts, we simply error out
retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250}
_, err := retry.Do[interface{}](ctx, 10, retryStrategy, func() (interface{}, error) {
err := l1Etl.db.Transaction(func(tx *database.DB) error {
if err := tx.Blocks.StoreL1BlockHeaders(l1BlockHeaders); err != nil {
return err
}
// we must have logs if we have l1 blocks
if err := tx.ContractEvents.StoreL1ContractEvents(l1ContractEvents); err != nil {
return err
}
return nil
})
if err != nil {
batch.Logger.Error("unable to persist batch", "err", err)
return nil, err
}
// a-ok! Can merge with the above block but being explicit
return nil, nil
})
if err != nil {
return err
}
batch.Logger.Info("indexed batch")
}
}
}
package etl
import (
"context"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type L2ETL struct {
ETL
db *database.DB
}
func NewL2ETL(log log.Logger, db *database.DB, client node.EthClient) (*L2ETL, error) {
log = log.New("etl", "l2")
// allow predeploys to be overridable
l2Contracts := []common.Address{}
for name, addr := range predeploys.Predeploys {
log.Info("configured contract", "name", name, "addr", addr)
l2Contracts = append(l2Contracts, *addr)
}
latestHeader, err := db.Blocks.L2LatestBlockHeader()
if err != nil {
return nil, err
}
var fromHeader *types.Header
if latestHeader != nil {
log.Info("detected last indexed block", "number", latestHeader.Number.Int, "hash", latestHeader.Hash)
fromHeader = latestHeader.RLPHeader.Header()
} else {
log.Info("no indexed state, starting from genesis")
}
etlBatches := make(chan ETLBatch)
etl := ETL{
log: log,
headerTraversal: node.NewHeaderTraversal(client, fromHeader),
ethClient: client.GethEthClient(),
contracts: l2Contracts,
etlBatches: etlBatches,
}
return &L2ETL{ETL: etl, db: db}, nil
}
func (l2Etl *L2ETL) Start(ctx context.Context) error {
errCh := make(chan error, 1)
go func() {
errCh <- l2Etl.ETL.Start(ctx)
}()
for {
select {
case err := <-errCh:
return err
// Index incoming batches
case batch := <-l2Etl.etlBatches:
// We're indexing every L2 block.
l2BlockHeaders := make([]database.L2BlockHeader, len(batch.Headers))
for i := range batch.Headers {
l2BlockHeaders[i] = database.L2BlockHeader{BlockHeader: database.BlockHeaderFromHeader(&batch.Headers[i])}
}
l2ContractEvents := make([]database.L2ContractEvent, len(batch.Logs))
for i := range batch.Logs {
timestamp := batch.HeaderMap[batch.Logs[i].BlockHash].Time
l2ContractEvents[i] = database.L2ContractEvent{ContractEvent: database.ContractEventFromLog(&batch.Logs[i], timestamp)}
}
// Continually try to persist this batch. If it fails after 5 attempts, we simply error out
retryStrategy := &retry.ExponentialStrategy{Min: 1000, Max: 20_000, MaxJitter: 250}
_, err := retry.Do[interface{}](ctx, 10, retryStrategy, func() (interface{}, error) {
err := l2Etl.db.Transaction(func(tx *database.DB) error {
if err := tx.Blocks.StoreL2BlockHeaders(l2BlockHeaders); err != nil {
return err
}
if len(l2ContractEvents) > 0 {
if err := tx.ContractEvents.StoreL2ContractEvents(l2ContractEvents); err != nil {
return err
}
}
return nil
})
if err != nil {
batch.Logger.Error("unable to persist batch", "err", err)
return nil, err
}
// a-ok! Can merge with the above block but being explicit
return nil, nil
})
if err != nil {
return err
}
batch.Logger.Info("indexed batch")
}
}
}
...@@ -3,14 +3,16 @@ package indexer ...@@ -3,14 +3,16 @@ package indexer
import ( import (
"context" "context"
"fmt" "fmt"
"runtime/debug"
"sync" "sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/config" "github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/etl"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/indexer/processor" "github.com/ethereum-optimism/optimism/indexer/processors"
) )
// Indexer contains the necessary resources for // Indexer contains the necessary resources for
...@@ -19,38 +21,47 @@ type Indexer struct { ...@@ -19,38 +21,47 @@ type Indexer struct {
db *database.DB db *database.DB
log log.Logger log log.Logger
L1Processor *processor.L1Processor L1ETL *etl.L1ETL
L2Processor *processor.L2Processor L2ETL *etl.L2ETL
BridgeProcessor *processors.BridgeProcessor
} }
// NewIndexer initializes an instance of the Indexer // NewIndexer initializes an instance of the Indexer
func NewIndexer(chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, db *database.DB, logger log.Logger) (*Indexer, error) { func NewIndexer(logger log.Logger, chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, db *database.DB) (*Indexer, error) {
l1Contracts := chainConfig.L1Contracts
l1EthClient, err := node.DialEthClient(rpcsConfig.L1RPC) l1EthClient, err := node.DialEthClient(rpcsConfig.L1RPC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
l1Processor, err := processor.NewL1Processor(logger, l1EthClient, db, l1Contracts)
l1Etl, err := etl.NewL1ETL(logger, db, l1EthClient, chainConfig.L1Contracts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// L2Processor (predeploys). Although most likely the right setting, make this configurable?
l2Contracts := processor.L2ContractPredeploys()
l2EthClient, err := node.DialEthClient(rpcsConfig.L2RPC) l2EthClient, err := node.DialEthClient(rpcsConfig.L2RPC)
if err != nil { if err != nil {
return nil, err return nil, err
} }
l2Processor, err := processor.NewL2Processor(logger, l2EthClient, db, l2Contracts)
// Currently defaults to the predeploys
l2Etl, err := etl.NewL2ETL(logger, db, l2EthClient)
if err != nil {
return nil, err
}
bridgeProcessor, err := processors.NewBridgeProcessor(logger, db, chainConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
indexer := &Indexer{ indexer := &Indexer{
db: db, db: db,
log: logger, log: logger,
L1Processor: l1Processor,
L2Processor: l2Processor, L1ETL: l1Etl,
L2ETL: l2Etl,
BridgeProcessor: bridgeProcessor,
} }
return indexer, nil return indexer, nil
...@@ -59,39 +70,41 @@ func NewIndexer(chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, db ...@@ -59,39 +70,41 @@ func NewIndexer(chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, db
// Start starts the indexing service on L1 and L2 chains // Start starts the indexing service on L1 and L2 chains
func (i *Indexer) Run(ctx context.Context) error { func (i *Indexer) Run(ctx context.Context) error {
var wg sync.WaitGroup var wg sync.WaitGroup
errCh := make(chan error, 1) errCh := make(chan error, 3)
// If either processor errors out, we stop // If either processor errors out, we stop
processorCtx, cancel := context.WithCancel(ctx) subCtx, cancel := context.WithCancel(ctx)
run := func(start func(ctx context.Context) error) { run := func(start func(ctx context.Context) error) {
wg.Add(1) wg.Add(1)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
i.log.Error("halting indexer on panic", "err", err) i.log.Error("halting indexer on panic", "err", err)
debug.PrintStack()
errCh <- fmt.Errorf("panic: %v", err) errCh <- fmt.Errorf("panic: %v", err)
} }
cancel()
wg.Done() wg.Done()
}() }()
err := start(processorCtx) err := start(subCtx)
if err != nil { if err != nil {
i.log.Error("halting indexer on error", "err", err) i.log.Error("halting indexer on error", "err", err)
cancel()
} }
// Send a value down regardless if we've received an error or halted // Send a value down regardless if we've received an error
// via cancellation where err == nil // or halted via cancellation where err == nil
errCh <- err errCh <- err
} }
// Kick off the processors // Kick off all the dependent routines
go run(i.L1Processor.Start) go run(i.L1ETL.Start)
go run(i.L2Processor.Start) go run(i.L2ETL.Start)
go run(i.BridgeProcessor.Start)
err := <-errCh err := <-errCh
// ensure both processors have halted before returning
wg.Wait() wg.Wait()
i.log.Info("indexer stopped")
return err return err
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
[chain] [chain]
# OP Goerli # OP Goerli
preset = 420 preset = 420
[rpcs] [rpcs]
l1-rpc = "${INDEXER_RPC_URL_L1}" l1-rpc = "${INDEXER_RPC_URL_L1}"
l2-rpc = "${INDEXER_RPC_URL_L2}" l2-rpc = "${INDEXER_RPC_URL_L2}"
......
...@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers ( ...@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers (
rlp_bytes VARCHAR NOT NULL rlp_bytes VARCHAR NOT NULL
); );
/** /**
* EVENT DATA * EVENT DATA
*/ */
...@@ -116,10 +116,6 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits ( ...@@ -116,10 +116,6 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits (
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid), initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
-- OptimismPortal specific
version UINT256 NOT NULL,
opaque_data VARCHAR NOT NULL,
-- transaction data -- transaction data
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
...@@ -152,9 +148,10 @@ CREATE TABLE IF NOT EXISTS l2_transaction_withdrawals ( ...@@ -152,9 +148,10 @@ CREATE TABLE IF NOT EXISTS l2_transaction_withdrawals (
-- CrossDomainMessenger -- CrossDomainMessenger
CREATE TABLE IF NOT EXISTS l1_bridge_messages( CREATE TABLE IF NOT EXISTS l1_bridge_messages(
nonce UINT256 NOT NULL PRIMARY KEY, message_hash VARCHAR NOT NULL PRIMARY KEY,
message_hash VARCHAR NOT NULL, nonce UINT256 NOT NULL UNIQUE,
transaction_source_hash VARCHAR NOT NULL REFERENCES l1_transaction_deposits(source_hash),
transaction_source_hash VARCHAR NOT NULL UNIQUE REFERENCES l1_transaction_deposits(source_hash),
sent_message_event_guid VARCHAR NOT NULL UNIQUE REFERENCES l1_contract_events(guid), sent_message_event_guid VARCHAR NOT NULL UNIQUE REFERENCES l1_contract_events(guid),
relayed_message_event_guid VARCHAR UNIQUE REFERENCES l2_contract_events(guid), relayed_message_event_guid VARCHAR UNIQUE REFERENCES l2_contract_events(guid),
...@@ -168,9 +165,10 @@ CREATE TABLE IF NOT EXISTS l1_bridge_messages( ...@@ -168,9 +165,10 @@ CREATE TABLE IF NOT EXISTS l1_bridge_messages(
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_bridge_messages( CREATE TABLE IF NOT EXISTS l2_bridge_messages(
nonce UINT256 NOT NULL PRIMARY KEY, message_hash VARCHAR NOT NULL PRIMARY KEY,
message_hash VARCHAR NOT NULL, nonce UINT256 NOT NULL UNIQUE,
transaction_withdrawal_hash VARCHAR NOT NULL REFERENCES l2_transaction_withdrawals(withdrawal_hash),
transaction_withdrawal_hash VARCHAR NOT NULL UNIQUE REFERENCES l2_transaction_withdrawals(withdrawal_hash),
sent_message_event_guid VARCHAR NOT NULL UNIQUE REFERENCES l2_contract_events(guid), sent_message_event_guid VARCHAR NOT NULL UNIQUE REFERENCES l2_contract_events(guid),
relayed_message_event_guid VARCHAR UNIQUE REFERENCES l1_contract_events(guid), relayed_message_event_guid VARCHAR UNIQUE REFERENCES l1_contract_events(guid),
...@@ -188,32 +186,32 @@ CREATE TABLE IF NOT EXISTS l2_bridge_messages( ...@@ -188,32 +186,32 @@ CREATE TABLE IF NOT EXISTS l2_bridge_messages(
CREATE TABLE IF NOT EXISTS l1_bridge_deposits ( CREATE TABLE IF NOT EXISTS l1_bridge_deposits (
transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash), transaction_source_hash VARCHAR PRIMARY KEY REFERENCES l1_transaction_deposits(source_hash),
-- We allow the cross_domain_messenger_nonce to be NULL-able to account -- We allow the cross_domain_message_hash to be NULL-able to account
-- for scenarios where ETH is simply sent to the OptimismPortal contract -- for scenarios where ETH is simply sent to the OptimismPortal contract
cross_domain_messenger_nonce UINT256 UNIQUE REFERENCES l1_bridge_messages(nonce), cross_domain_message_hash VARCHAR UNIQUE REFERENCES l1_bridge_messages(message_hash),
-- Deposit information -- Deposit information
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL, -- REFERENCES l1_tokens(address), uncomment me in future pr local_token_address VARCHAR NOT NULL, -- REFERENCES l1_tokens(address), uncomment me in future pr
l2_token_address VARCHAR NOT NULL, -- REFERENCES l2_tokens(address), uncomment me in future pr remote_token_address VARCHAR NOT NULL, -- REFERENCES l2_tokens(address), uncomment me in future pr
amount UINT256 NOT NULL, amount UINT256 NOT NULL,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals ( CREATE TABLE IF NOT EXISTS l2_bridge_withdrawals (
transaction_withdrawal_hash VARCHAR PRIMARY KEY REFERENCES l2_transaction_withdrawals(withdrawal_hash), transaction_withdrawal_hash VARCHAR PRIMARY KEY REFERENCES l2_transaction_withdrawals(withdrawal_hash),
-- We allow the cross_domain_messenger_nonce to be NULL-able to account for -- We allow the cross_domain_message_hash to be NULL-able to account for
-- scenarios where ETH is simply sent to the L2ToL1MessagePasser contract -- scenarios where ETH is simply sent to the L2ToL1MessagePasser contract
cross_domain_messenger_nonce UINT256 UNIQUE REFERENCES l2_bridge_messages(nonce), cross_domain_message_hash VARCHAR UNIQUE REFERENCES l2_bridge_messages(message_hash),
-- Withdrawal information -- Withdrawal information
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL, to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL, -- REFERENCES l1_tokens(address), uncomment me in future pr local_token_address VARCHAR NOT NULL, -- REFERENCES l2_tokens(address), uncomment me in future pr
l2_token_address VARCHAR NOT NULL, -- REFERENCES l2_tokens(address), uncomment me in future pr remote_token_address VARCHAR NOT NULL, -- REFERENCES l1_tokens(address), uncomment me in future pr
amount UINT256 NOT NULL, amount UINT256 NOT NULL,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL CHECK (timestamp > 0) timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
...@@ -27,12 +27,13 @@ const ( ...@@ -27,12 +27,13 @@ const (
type EthClient interface { type EthClient interface {
FinalizedBlockHeight() (*big.Int, error) FinalizedBlockHeight() (*big.Int, error)
BlockHeadersByRange(*big.Int, *big.Int) ([]*types.Header, error) BlockHeadersByRange(*big.Int, *big.Int) ([]types.Header, error)
BlockHeaderByHash(common.Hash) (*types.Header, error) BlockHeaderByHash(common.Hash) (*types.Header, error)
StorageHash(common.Address, *big.Int) (common.Hash, error) StorageHash(common.Address, *big.Int) (common.Hash, error)
RawRpcClient() *rpc.Client GethRpcClient() *rpc.Client
GethEthClient() *ethclient.Client
} }
type client struct { type client struct {
...@@ -56,10 +57,14 @@ func NewEthClient(rpcClient *rpc.Client) EthClient { ...@@ -56,10 +57,14 @@ func NewEthClient(rpcClient *rpc.Client) EthClient {
return &client{rpcClient} return &client{rpcClient}
} }
func (c *client) RawRpcClient() *rpc.Client { func (c *client) GethRpcClient() *rpc.Client {
return c.rpcClient return c.rpcClient
} }
func (c *client) GethEthClient() *ethclient.Client {
return ethclient.NewClient(c.GethRpcClient())
}
// FinalizedBlockHeight retrieves the latest block height in a finalized state // FinalizedBlockHeight retrieves the latest block height in a finalized state
func (c *client) FinalizedBlockHeight() (*big.Int, error) { func (c *client) FinalizedBlockHeight() (*big.Int, error) {
ctxwt, cancel := context.WithTimeout(context.Background(), defaultRequestTimeout) ctxwt, cancel := context.WithTimeout(context.Background(), defaultRequestTimeout)
...@@ -97,7 +102,7 @@ func (c *client) BlockHeaderByHash(hash common.Hash) (*types.Header, error) { ...@@ -97,7 +102,7 @@ func (c *client) BlockHeaderByHash(hash common.Hash) (*types.Header, error) {
// BlockHeadersByRange will retrieve block headers within the specified range -- includsive. No restrictions // BlockHeadersByRange will retrieve block headers within the specified range -- includsive. No restrictions
// are placed on the range such as blocks in the "latest", "safe" or "finalized" states. If the specified // are placed on the range such as blocks in the "latest", "safe" or "finalized" states. If the specified
// range is too large, `endHeight > latest`, the resulting list is truncated to the available headers // range is too large, `endHeight > latest`, the resulting list is truncated to the available headers
func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]*types.Header, error) { func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]types.Header, error) {
count := new(big.Int).Sub(endHeight, startHeight).Uint64() + 1 count := new(big.Int).Sub(endHeight, startHeight).Uint64() + 1
batchElems := make([]rpc.BatchElem, count) batchElems := make([]rpc.BatchElem, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
...@@ -121,7 +126,7 @@ func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]*types. ...@@ -121,7 +126,7 @@ func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]*types.
// - Ensure integrity that they build on top of each other // - Ensure integrity that they build on top of each other
// - Truncate out headers that do not exist (endHeight > "latest") // - Truncate out headers that do not exist (endHeight > "latest")
size := 0 size := 0
headers := make([]*types.Header, count) headers := make([]types.Header, count)
for i, batchElem := range batchElems { for i, batchElem := range batchElems {
if batchElem.Error != nil { if batchElem.Error != nil {
return nil, batchElem.Error return nil, batchElem.Error
...@@ -129,17 +134,19 @@ func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]*types. ...@@ -129,17 +134,19 @@ func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]*types.
break break
} }
header := batchElem.Result.(*types.Header) header, ok := batchElem.Result.(*types.Header)
if !ok {
return nil, fmt.Errorf("unable to transform rpc response %v into types.Header", batchElem.Result)
}
if i > 0 && header.ParentHash != headers[i-1].Hash() { if i > 0 && header.ParentHash != headers[i-1].Hash() {
// Warn here that we got a bad (malicious?) response return nil, fmt.Errorf("queried header %s does not follow parent %s", header.Hash(), headers[i-1].Hash())
break
} }
headers[i] = header headers[i] = *header
size = size + 1 size = size + 1
} }
headers = headers[:size]
headers = headers[:size]
return headers, nil return headers, nil
} }
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
...@@ -21,9 +22,9 @@ func (m *MockEthClient) FinalizedBlockHeight() (*big.Int, error) { ...@@ -21,9 +22,9 @@ func (m *MockEthClient) FinalizedBlockHeight() (*big.Int, error) {
return args.Get(0).(*big.Int), args.Error(1) return args.Get(0).(*big.Int), args.Error(1)
} }
func (m *MockEthClient) BlockHeadersByRange(from, to *big.Int) ([]*types.Header, error) { func (m *MockEthClient) BlockHeadersByRange(from, to *big.Int) ([]types.Header, error) {
args := m.Called(from, to) args := m.Called(from, to)
return args.Get(0).([]*types.Header), args.Error(1) return args.Get(0).([]types.Header), args.Error(1)
} }
func (m *MockEthClient) BlockHeaderByHash(hash common.Hash) (*types.Header, error) { func (m *MockEthClient) BlockHeaderByHash(hash common.Hash) (*types.Header, error) {
...@@ -36,7 +37,12 @@ func (m *MockEthClient) StorageHash(address common.Address, blockNumber *big.Int ...@@ -36,7 +37,12 @@ func (m *MockEthClient) StorageHash(address common.Address, blockNumber *big.Int
return args.Get(0).(common.Hash), args.Error(1) return args.Get(0).(common.Hash), args.Error(1)
} }
func (m *MockEthClient) RawRpcClient() *rpc.Client { func (m *MockEthClient) GethRpcClient() *rpc.Client {
args := m.Called() args := m.Called()
return args.Get(0).(*rpc.Client) return args.Get(0).(*rpc.Client)
} }
func (m *MockEthClient) GethEthClient() *ethclient.Client {
args := m.Called()
return args.Get(0).(*ethclient.Client)
}
...@@ -26,7 +26,7 @@ func NewHeaderTraversal(ethClient EthClient, fromHeader *types.Header) *HeaderTr ...@@ -26,7 +26,7 @@ func NewHeaderTraversal(ethClient EthClient, fromHeader *types.Header) *HeaderTr
// NextFinalizedHeaders retrives the next set of headers that have been // NextFinalizedHeaders retrives the next set of headers that have been
// marked as finalized by the connected client, bounded by the supplied size // marked as finalized by the connected client, bounded by the supplied size
func (f *HeaderTraversal) NextFinalizedHeaders(maxSize uint64) ([]*types.Header, error) { func (f *HeaderTraversal) NextFinalizedHeaders(maxSize uint64) ([]types.Header, error) {
finalizedBlockHeight, err := f.ethClient.FinalizedBlockHeight() finalizedBlockHeight, err := f.ethClient.FinalizedBlockHeight()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -61,6 +61,6 @@ func (f *HeaderTraversal) NextFinalizedHeaders(maxSize uint64) ([]*types.Header, ...@@ -61,6 +61,6 @@ func (f *HeaderTraversal) NextFinalizedHeaders(maxSize uint64) ([]*types.Header,
return nil, ErrHeaderTraversalAndProviderMismatchedState return nil, ErrHeaderTraversalAndProviderMismatchedState
} }
f.lastHeader = headers[numHeaders-1] f.lastHeader = &headers[numHeaders-1]
return headers, nil return headers, nil
} }
...@@ -11,22 +11,21 @@ import ( ...@@ -11,22 +11,21 @@ import (
) )
// make a set of headers which chain correctly // make a set of headers which chain correctly
func makeHeaders(numHeaders uint64, prevHeader *types.Header) []*types.Header { func makeHeaders(numHeaders uint64, prevHeader *types.Header) []types.Header {
headers := make([]*types.Header, numHeaders) headers := make([]types.Header, numHeaders)
for i := range headers { for i := range headers {
if i == 0 { if i == 0 {
if prevHeader == nil { if prevHeader == nil {
// genesis // genesis
headers[i] = &types.Header{Number: big.NewInt(0)} headers[i] = types.Header{Number: big.NewInt(0)}
} else { } else {
// chain onto the previous header // chain onto the previous header
headers[i] = &types.Header{Number: big.NewInt(prevHeader.Number.Int64() + 1)} headers[i] = types.Header{Number: big.NewInt(prevHeader.Number.Int64() + 1)}
headers[i].ParentHash = prevHeader.Hash() headers[i].ParentHash = prevHeader.Hash()
} }
} else { } else {
prevHeader = headers[i-1] headers[i] = types.Header{Number: big.NewInt(headers[i-1].Number.Int64() + 1)}
headers[i] = &types.Header{Number: big.NewInt(prevHeader.Number.Int64() + 1)} headers[i].ParentHash = headers[i-1].Hash()
headers[i].ParentHash = prevHeader.Hash()
} }
} }
...@@ -62,7 +61,7 @@ func TestHeaderTraversalNextFinalizedHeadersCursored(t *testing.T) { ...@@ -62,7 +61,7 @@ func TestHeaderTraversalNextFinalizedHeadersCursored(t *testing.T) {
require.Len(t, headers, 5) require.Len(t, headers, 5)
// blocks [5..9] // blocks [5..9]
headers = makeHeaders(5, headers[len(headers)-1]) headers = makeHeaders(5, &headers[len(headers)-1])
client.On("FinalizedBlockHeight").Return(big.NewInt(9), nil) client.On("FinalizedBlockHeight").Return(big.NewInt(9), nil)
client.On("BlockHeadersByRange", mock.MatchedBy(bigIntMatcher(5)), mock.MatchedBy(bigIntMatcher(9))).Return(headers, nil) client.On("BlockHeadersByRange", mock.MatchedBy(bigIntMatcher(5)), mock.MatchedBy(bigIntMatcher(9))).Return(headers, nil)
headers, err = headerTraversal.NextFinalizedHeaders(5) headers, err = headerTraversal.NextFinalizedHeaders(5)
...@@ -87,7 +86,7 @@ func TestHeaderTraversalNextFinalizedHeadersMaxSize(t *testing.T) { ...@@ -87,7 +86,7 @@ func TestHeaderTraversalNextFinalizedHeadersMaxSize(t *testing.T) {
require.Len(t, headers, 5) require.Len(t, headers, 5)
// clamped by the supplied size. FinalizedHeight == 100 // clamped by the supplied size. FinalizedHeight == 100
headers = makeHeaders(10, headers[len(headers)-1]) headers = makeHeaders(10, &headers[len(headers)-1])
client.On("BlockHeadersByRange", mock.MatchedBy(bigIntMatcher(5)), mock.MatchedBy(bigIntMatcher(14))).Return(headers, nil) client.On("BlockHeadersByRange", mock.MatchedBy(bigIntMatcher(5)), mock.MatchedBy(bigIntMatcher(14))).Return(headers, nil)
headers, err = headerTraversal.NextFinalizedHeaders(10) headers, err = headerTraversal.NextFinalizedHeaders(10)
require.NoError(t, err) require.NoError(t, err)
......
package processor
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
)
var (
// Standard ABI types copied from golang ABI tests
Uint256Type, _ = abi.NewType("uint256", "", nil)
BytesType, _ = abi.NewType("bytes", "", nil)
AddressType, _ = abi.NewType("address", "", nil)
LegacyCrossDomainMessengerRelayMessageMethod = abi.NewMethod(
"relayMessage",
"relayMessage",
abi.Function,
"external", // mutability
false, // isConst
true, // payable
abi.Arguments{ // inputs
{Name: "sender", Type: AddressType},
{Name: "target", Type: AddressType},
{Name: "data", Type: BytesType},
{Name: "nonce", Type: Uint256Type},
},
abi.Arguments{}, // outputs
)
)
type CrossDomainMessengerSentMessageEvent struct {
*bindings.CrossDomainMessengerSentMessage
Value *big.Int
MessageHash common.Hash
Event *database.ContractEvent
}
type CrossDomainMessengerRelayedMessageEvent struct {
*bindings.CrossDomainMessengerRelayedMessage
Event *database.ContractEvent
}
func CrossDomainMessengerSentMessageEvents(events *ProcessedContractEvents) ([]CrossDomainMessengerSentMessageEvent, error) {
crossDomainMessengerABI, err := bindings.CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return nil, err
}
sentMessageEventAbi := crossDomainMessengerABI.Events["SentMessage"]
sentMessageEventExtensionAbi := crossDomainMessengerABI.Events["SentMessageExtension1"]
processedSentMessageEvents := events.eventsBySignature[sentMessageEventAbi.ID]
crossDomainMessageEvents := make([]CrossDomainMessengerSentMessageEvent, len(processedSentMessageEvents))
for i, sentMessageEvent := range processedSentMessageEvents {
log := sentMessageEvent.RLPLog
var sentMsgData bindings.CrossDomainMessengerSentMessage
sentMsgData.Raw = *log
err = UnpackLog(&sentMsgData, log, sentMessageEventAbi.Name, crossDomainMessengerABI)
if err != nil {
return nil, err
}
var sentMsgExtensionData bindings.CrossDomainMessengerSentMessageExtension1
extensionLog := events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].RLPLog
sentMsgExtensionData.Raw = *extensionLog
err = UnpackLog(&sentMsgExtensionData, extensionLog, sentMessageEventExtensionAbi.Name, crossDomainMessengerABI)
if err != nil {
return nil, err
}
msgHash, err := CrossDomainMessageHash(crossDomainMessengerABI, &sentMsgData, sentMsgExtensionData.Value)
if err != nil {
return nil, err
}
crossDomainMessageEvents[i] = CrossDomainMessengerSentMessageEvent{
CrossDomainMessengerSentMessage: &sentMsgData,
Value: sentMsgExtensionData.Value,
MessageHash: msgHash,
Event: sentMessageEvent,
}
}
return crossDomainMessageEvents, nil
}
func CrossDomainMessengerRelayedMessageEvents(events *ProcessedContractEvents) ([]CrossDomainMessengerRelayedMessageEvent, error) {
crossDomainMessengerABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return nil, err
}
relayedMessageEventAbi := crossDomainMessengerABI.Events["RelayedMessage"]
processedRelayedMessageEvents := events.eventsBySignature[relayedMessageEventAbi.ID]
crossDomainMessageEvents := make([]CrossDomainMessengerRelayedMessageEvent, len(processedRelayedMessageEvents))
for i, relayedMessageEvent := range processedRelayedMessageEvents {
log := relayedMessageEvent.RLPLog
var relayedMsgData bindings.CrossDomainMessengerRelayedMessage
relayedMsgData.Raw = *log
err = UnpackLog(&relayedMsgData, log, relayedMessageEventAbi.Name, crossDomainMessengerABI)
if err != nil {
return nil, err
}
crossDomainMessageEvents[i] = CrossDomainMessengerRelayedMessageEvent{
CrossDomainMessengerRelayedMessage: &relayedMsgData,
Event: relayedMessageEvent,
}
}
return crossDomainMessageEvents, nil
}
// Replica of `Hashing.sol#hashCrossDomainMessage` solidity implementation
func CrossDomainMessageHash(abi *abi.ABI, sentMsg *bindings.CrossDomainMessengerSentMessage, value *big.Int) (common.Hash, error) {
version, _ := DecodeVersionedNonce(sentMsg.MessageNonce)
switch version {
case 0:
// Legacy Message
inputBytes, err := LegacyCrossDomainMessengerRelayMessageMethod.Inputs.Pack(sentMsg.Sender, sentMsg.Target, sentMsg.Message, sentMsg.MessageNonce)
if err != nil {
return common.Hash{}, err
}
msgBytes := append(LegacyCrossDomainMessengerRelayMessageMethod.ID, inputBytes...)
return crypto.Keccak256Hash(msgBytes), nil
case 1:
// Current Message
msgBytes, err := abi.Pack("relayMessage", sentMsg.MessageNonce, sentMsg.Sender, sentMsg.Target, value, sentMsg.GasLimit, sentMsg.Message)
if err != nil {
return common.Hash{}, err
}
return crypto.Keccak256Hash(msgBytes), nil
}
return common.Hash{}, fmt.Errorf("unsupported cross domain messenger version: %d", version)
}
package processor
import (
"encoding/binary"
"math/big"
)
// DecodeVersionNonce is an re-implementation of Encoding.sol#decodeVersionedNonce.
// If the nonce is greater than 32 bytes (solidity uint256), bytes [32:] are ignored
func DecodeVersionedNonce(nonce *big.Int) (uint16, *big.Int) {
nonceBytes := nonce.Bytes()
nonceByteLen := len(nonceBytes)
if nonceByteLen < 30 {
// version is 0x0000
return 0, nonce
} else if nonceByteLen == 31 {
// version is 0x00[01..ff]
return uint16(nonceBytes[0]), new(big.Int).SetBytes(nonceBytes[1:])
} else {
// fully specified
version := binary.BigEndian.Uint16(nonceBytes[:2])
return version, new(big.Int).SetBytes(nonceBytes[2:])
}
}
This diff is collapsed.
This diff is collapsed.
package processor
import (
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
)
type L2ToL1MessagePasserMessagePassed struct {
*bindings.L2ToL1MessagePasserMessagePassed
Event *database.ContractEvent
}
func L2ToL1MessagePasserMessagesPassed(events *ProcessedContractEvents) ([]L2ToL1MessagePasserMessagePassed, error) {
l2ToL1MessagePasserAbi, err := bindings.L2ToL1MessagePasserMetaData.GetAbi()
if err != nil {
return nil, err
}
eventName := "MessagePassed"
processedMessagePassedEvents := events.eventsBySignature[l2ToL1MessagePasserAbi.Events[eventName].ID]
messagesPassed := make([]L2ToL1MessagePasserMessagePassed, len(processedMessagePassedEvents))
for i, messagePassedEvent := range processedMessagePassedEvents {
log := messagePassedEvent.RLPLog
var messagePassed bindings.L2ToL1MessagePasserMessagePassed
messagePassed.Raw = *log
err := UnpackLog(&messagePassed, log, eventName, l2ToL1MessagePasserAbi)
if err != nil {
return nil, err
}
messagesPassed[i] = L2ToL1MessagePasserMessagePassed{
L2ToL1MessagePasserMessagePassed: &messagePassed,
Event: messagePassedEvent,
}
}
return messagesPassed, nil
}
This diff is collapsed.
package processor
import (
"context"
"time"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
const (
defaultLoopInterval = 5 * time.Second
defaultHeaderBufferSize = 500
)
// ProcessFn is the the entrypoint for processing a batch of headers.
// In the event of failure, database operations are rolled back
type ProcessFn func(*database.DB, []*types.Header) error
type processor struct {
headerTraversal *node.HeaderTraversal
db *database.DB
processFn ProcessFn
processLog log.Logger
paused bool
latestProcessedHeader *types.Header
}
// Start kicks off the processing loop. This is a block operation
// unless the processor encountering an error, abrupting the loop,
// or the supplied context is cancelled.
func (p *processor) Start(ctx context.Context) error {
done := ctx.Done()
pollTicker := time.NewTicker(defaultLoopInterval)
defer pollTicker.Stop()
p.processLog.Info("starting processor...")
var unprocessedHeaders []*types.Header
for {
select {
case <-done:
p.processLog.Info("stopping processor")
return nil
case <-pollTicker.C:
if p.paused {
p.processLog.Warn("processor is paused...")
continue
}
if len(unprocessedHeaders) == 0 {
newHeaders, err := p.headerTraversal.NextFinalizedHeaders(defaultHeaderBufferSize)
if err != nil {
p.processLog.Error("error querying for headers", "err", err)
continue
} else if len(newHeaders) == 0 {
// Logged as an error since this loop should be operating at a longer interval than the provider
p.processLog.Error("no new headers. processor unexpectedly at head...")
continue
}
unprocessedHeaders = newHeaders
} else {
p.processLog.Info("retrying previous batch")
}
firstHeader := unprocessedHeaders[0]
lastHeader := unprocessedHeaders[len(unprocessedHeaders)-1]
batchLog := p.processLog.New("batch_start_block_number", firstHeader.Number, "batch_end_block_number", lastHeader.Number)
err := p.db.Transaction(func(db *database.DB) error {
batchLog.Info("processing batch")
return p.processFn(db, unprocessedHeaders)
})
// Eventually, we want to halt the processor on any error rather than rely
// on this loop for retry functionality.
if err != nil {
batchLog.Warn("error processing batch. no operations committed", "err", err)
} else {
batchLog.Info("fully committed batch")
unprocessedHeaders = nil
p.latestProcessedHeader = lastHeader
}
}
}
}
func (p processor) LatestProcessedHeader() *types.Header {
return p.latestProcessedHeader
}
// Useful ONLY for tests!
func (p *processor) PauseForTest() {
p.paused = true
}
func (p *processor) ResumeForTest() {
p.paused = false
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package bridge
import "github.com/ethereum/go-ethereum/common"
type logKey struct {
BlockHash common.Hash
LogIndex uint64
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -17,7 +17,7 @@ test: ...@@ -17,7 +17,7 @@ test:
go test -v ./... go test -v ./...
lint: lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" ./...
fuzz: fuzz:
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelConfig_CheckTimeout ./batcher go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelConfig_CheckTimeout ./batcher
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment