Commit 093b7588 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #3697 from ethereum-optimism/develop

Develop -> Master
parents 302700d6 14719116
---
'@eth-optimism/proxyd': minor
---
Fixed JSON-RPC 2.0 specification compliance by adding the optional data field on an RPCError
---
'@eth-optimism/contracts-bedrock': patch
---
Use uint64 for arithmetic in XDM's baseGas
---
'@eth-optimism/sdk': patch
---
Adds contract addresses for the Bedrock Alpha testnet
---
'@eth-optimism/contracts-bedrock': patch
'@eth-optimism/sdk': patch
---
Rename the event emitted in the L2ToL1MessagePasser
---
'@eth-optimism/contracts-periphery': patch
---
Goerli nft bridge deployment
---
'@eth-optimism/l2geth': patch
---
add --rpc.evmtimeout flag to configure timeout for eth_call
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/contracts-periphery': patch
---
Fix erc721 factory to match erc21 factory
---
'@eth-optimism/indexer': minor
---
Bedrock support
---
'@eth-optimism/proxyd': minor
---
Adds new Redis rate limiter
---
"@eth-optimism/contracts-periphery": patch
---
mainnet nft bridge deployments
---
'@eth-optimism/contracts-bedrock': patch
---
Removes an unnecessary initializer parameter in the L200
---
'@eth-optimism/endpoint-monitor': major
---
Initial release of endpoint monitor
---
'@eth-optimism/ci-builder': patch
---
Pin slither version to 0.9.0
...@@ -145,7 +145,7 @@ jobs: ...@@ -145,7 +145,7 @@ jobs:
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run: - run:
name: upload coverage name: upload coverage
command: codecov --verbose --clean --flag contracts-bedrock-forge command: codecov --verbose --clean --flag contracts-bedrock-tests
environment: environment:
FOUNDRY_PROFILE: ci FOUNDRY_PROFILE: ci
- run: - run:
...@@ -190,6 +190,9 @@ jobs: ...@@ -190,6 +190,9 @@ jobs:
description: Regex matching dependent packages description: Regex matching dependent packages
type: string type: string
default: this-package-does-not-exist default: this-package-does-not-exist
coverage_flag:
description: Coverage flag name
type: string
docker: docker:
- image: ethereumoptimism/ci-builder:latest - image: ethereumoptimism/ci-builder:latest
resource_class: large resource_class: large
...@@ -214,9 +217,7 @@ jobs: ...@@ -214,9 +217,7 @@ jobs:
working_directory: packages/<<parameters.package_name>> working_directory: packages/<<parameters.package_name>>
- run: - run:
name: Upload coverage name: Upload coverage
command: | command: codecov --verbose --clean --flag <<parameters.coverage_flag>>
echo <<parameters.package_name>> # TEMP for debugging
codecov --verbose --clean --flag <<parameters.package_name>>
bedrock-go-tests: bedrock-go-tests:
docker: docker:
...@@ -286,9 +287,14 @@ jobs: ...@@ -286,9 +287,14 @@ jobs:
gotestsum --junitfile /test-results/op-batcher.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./... gotestsum --junitfile /test-results/op-batcher.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
working_directory: op-batcher working_directory: op-batcher
- run: - run:
name: test op-e2e name: test op-e2e (WS)
command: |
gotestsum --format standard-verbose --junitfile /test-results/op-e2e.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
working_directory: op-e2e
- run:
name: test op-e2e (HTTP)
command: | command: |
gotestsum --junitfile /test-results/op-e2e.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./... OP_E2E_USE_HTTP=true gotestsum --junitfile /test-results/op-e2e.xml -- -coverpkg=github.com/ethereum-optimism/optimism/... -coverprofile=coverage.out -covermode=atomic ./...
working_directory: op-e2e working_directory: op-e2e
- run: - run:
name: test op-service name: test op-service
...@@ -426,7 +432,7 @@ jobs: ...@@ -426,7 +432,7 @@ jobs:
name: Test name: Test
command: | command: |
mkdir -p /test-results mkdir -p /test-results
gotestsum --junitfile /test-results/tests.xml DB_USER=postgres gotestsum --junitfile /test-results/tests.xml
working_directory: <<parameters.working_directory>> working_directory: <<parameters.working_directory>>
- when: - when:
condition: condition:
...@@ -632,17 +638,20 @@ workflows: ...@@ -632,17 +638,20 @@ workflows:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: common-ts-tests name: common-ts-tests
coverage_flag: common-ts-tests
package_name: common-ts package_name: common-ts
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: contracts-tests name: contracts-tests
coverage_flag: contracts-tests
package_name: contracts package_name: contracts
dependencies: hardhat-deploy-config dependencies: hardhat-deploy-config
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: core-utils-tests name: core-utils-tests
coverage_flag: core-utils-tests
package_name: core-utils package_name: core-utils
requires: requires:
- yarn-monorepo - yarn-monorepo
...@@ -654,54 +663,63 @@ workflows: ...@@ -654,54 +663,63 @@ workflows:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: actor-tests-tests name: actor-tests-tests
coverage_flag: actor-tests-tests
package_name: actor-tests package_name: actor-tests
dependencies: "(core-utils|sdk)" dependencies: "(core-utils|sdk)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: contracts-governance-tests name: contracts-governance-tests
coverage_flag: contracts-governance-tests
package_name: contracts-governance package_name: contracts-governance
dependencies: "(core-utils|sdk)" dependencies: "(core-utils|sdk)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: contracts-periphery-tests name: contracts-periphery-tests
coverage_flag: contracts-periphery-tests
package_name: contracts-periphery package_name: contracts-periphery
dependencies: "(contracts|contracts-bedrock|core-utils|hardhat-deploy-config)" dependencies: "(contracts|contracts-bedrock|core-utils|hardhat-deploy-config)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: dtl-tests name: dtl-tests
coverage_flag: dtl-tests
package_name: data-transport-layer package_name: data-transport-layer
dependencies: "(common-ts|contracts|core-utils)" dependencies: "(common-ts|contracts|core-utils)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: drippie-mon-tests name: drippie-mon-tests
coverage_flag: drippie-mon-tests
package_name: drippie-mon package_name: drippie-mon
dependencies: "(common-ts|contracts-periphery|core-utils|sdk)" dependencies: "(common-ts|contracts-periphery|core-utils|sdk)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: fault-detector-tests name: fault-detector-tests
coverage_flag: fault-detector-tests
package_name: fault-detector package_name: fault-detector
dependencies: "(common-ts|contracts|core-utils|sdk)" dependencies: "(common-ts|contracts|core-utils|sdk)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: message-relayer-tests name: message-relayer-tests
coverage_flag: message-relayer-tests
package_name: message-relayer package_name: message-relayer
dependencies: "(common-ts|core-utils|sdk)" dependencies: "(common-ts|core-utils|sdk)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: replica-healthcheck-tests name: replica-healthcheck-tests
coverage_flag: replica-healthcheck-tests
package_name: replica-healthcheck package_name: replica-healthcheck
dependencies: "(common-ts|core-utils)" dependencies: "(common-ts|core-utils)"
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: sdk-tests name: sdk-tests
coverage_flag: sdk-tests
package_name: sdk package_name: sdk
dependencies: "(contracts|core-utils)" dependencies: "(contracts|core-utils)"
requires: requires:
......
...@@ -26,7 +26,7 @@ pull_request_rules: ...@@ -26,7 +26,7 @@ pull_request_rules:
actions: actions:
queue: queue:
name: default name: default
method: squash method: merge
- name: Add merge train label - name: Add merge train label
conditions: conditions:
- "queue-position >= 0" - "queue-position >= 0"
...@@ -99,7 +99,7 @@ pull_request_rules: ...@@ -99,7 +99,7 @@ pull_request_rules:
- name: Nag changesets - name: Nag changesets
conditions: conditions:
- and: - and:
- 'files~=\.((?<!\.spec\.)ts|go|js|mod|sum)$' - 'files~=\.((?<!\.spec\.)ts|(?<!\.t\.)sol|go|js|mod|sum)$'
- '-files~=^\.changeset/(.*)\.md' - '-files~=^\.changeset/(.*)\.md'
actions: actions:
comment: comment:
......
...@@ -32,6 +32,7 @@ jobs: ...@@ -32,6 +32,7 @@ jobs:
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }} batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
indexer: ${{ steps.packages.outputs.indexer }} indexer: ${{ steps.packages.outputs.indexer }}
teleportr: ${{ steps.packages.outputs.teleportr }} teleportr: ${{ steps.packages.outputs.teleportr }}
endpoint-monitor: ${{ steps.packages.outputs.l2geth-exporter }}
steps: steps:
- name: Check out source code - name: Check out source code
...@@ -603,3 +604,41 @@ jobs: ...@@ -603,3 +604,41 @@ jobs:
GITDATE=${{ steps.build_args.outputs.GITDATE }} GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }} GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }} GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
endpoint-monitor:
name: Publish endpoint-monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.endpoint-monitor != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./endpoint-monitor/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./endpoint-monitor/Dockerfile
push: true
tags: ethereumoptimism/endpoint-monitor:${{ needs.canary-publish.outputs.endpoint-monitor }}
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
...@@ -29,6 +29,7 @@ jobs: ...@@ -29,6 +29,7 @@ jobs:
teleportr: ${{ steps.packages.outputs.teleportr }} teleportr: ${{ steps.packages.outputs.teleportr }}
ci-builder: ${{ steps.packages.outputs.ci-builder }} ci-builder: ${{ steps.packages.outputs.ci-builder }}
foundry: ${{ steps.packages.outputs.foundry }} foundry: ${{ steps.packages.outputs.foundry }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
steps: steps:
- name: Checkout Repo - name: Checkout Repo
...@@ -635,3 +636,40 @@ jobs: ...@@ -635,3 +636,40 @@ jobs:
GITDATE=${{ steps.build_args.outputs.GITDATE }} GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }} GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }} GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
endpoint-monitor:
name: Publish endpoint-monitor Version ${{ needs.release.outputs.endpoint-monitor}}
needs: release
if: needs.release.outputs.endpoint-monitor != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set build args
id: build_args
run: |
echo ::set-output name=GITDATE::"$(date +%d-%m-%Y)"
echo ::set-output name=GITVERSION::$(jq -r .version ./endpoint-monitor/package.json)
echo ::set-output name=GITCOMMIT::"$GITHUB_SHA"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./endpoint-monitor/Dockerfile
push: true
tags: ethereumoptimism/endpoint-monitor:${{ needs.release.outputs.endpoint-monitor }},ethereumoptimism/endpoint-monitor:latest
build-args: |
GITDATE=${{ steps.build_args.outputs.GITDATE }}
GITCOMMIT=${{ steps.build_args.outputs.GITCOMMIT }}
GITVERSION=${{ steps.build_args.outputs.GITVERSION }}
...@@ -3,3 +3,21 @@ ignore: ...@@ -3,3 +3,21 @@ ignore:
- "l2geth" - "l2geth"
- "**/*.t.sol" - "**/*.t.sol"
- "op-bindings/bindings/*.go" - "op-bindings/bindings/*.go"
flag_management:
# Note: flags should have the same name as the circleci job in which they
# are uploaded.
individual_flags:
- name: bedrock-go-tests
- name: contracts-bedrock-tests
- name: common-ts-tests
- name: contracts-tests
- name: core-utils-tests
- name: actor-tests-tests
- name: contracts-governance-tests
- name: contracts-periphery-tests
- name: dtl-tests
- name: drippie-mon-tests
- name: fault-detector-tests
- name: message-relayer-tests
- name: replica-healthcheck-tests
- name: sdk-tests
export ENDPOINT_MONITOR_LOG_LEVEL=debug
export ENDPOINT_MONITOR_PROVIDERS=goerli,mainnet
export ENDPOINT_MONITOR_GOERLI_URL=wss://ws-goerli.optimism.io
export ENDPOINT_MONITOR_MAINNET_URL=wss://ws-mainnet.optimism.io
# @eth-optimism/endpoint-monitor
FROM golang:1.18.0-alpine3.15 as builder
COPY ./endpoint-monitor /app
WORKDIR /app
RUN apk --no-cache add make jq bash git alpine-sdk
RUN make build
FROM alpine:3.15
RUN apk --no-cache add ca-certificates
RUN addgroup -S app && adduser -S app -G app
USER app
WORKDIR /app
COPY --from=builder /app/bin/endpoint-monitor /app
ENTRYPOINT ["/app/endpoint-monitor"]
GITCOMMIT := $(shell git rev-parse HEAD)
GITDATE := $(shell git show -s --format='%ct')
VERSION := v0.0.0
LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.Version=$(VERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
all: build
build:
env GO111MODULE=on go build -v $(LDFLAGS) -o ./bin/endpoint-monitor ./cmd
clean:
rm ./bin/endpoint-monitor
test:
go test -v ./...
lint:
golangci-lint run ./...
.PHONY: \
build \
clean \
test \
lint
# @eth-optimism/endpoint-monitor
The endpoint-monitor runs websocket checks on edge-proxyd endpoints and downstream infra provider endpoints.
## Setup
Install go1.18
```bash
make build
source .env.example # (or copy to .envrc if using direnv)
./bin/endpoint-monitor
```
package main
import (
"fmt"
"os"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
endpointMonitor "github.com/ethereum-optimism/optimism/endpoint-monitor"
)
var (
Version = ""
GitCommit = ""
GitDate = ""
)
func main() {
oplog.SetupDefaults()
app := cli.NewApp()
app.Flags = endpointMonitor.CLIFlags("ENDPOINT_MONITOR")
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate)
app.Name = "endpoint-monitor"
app.Usage = "Endpoint Monitoring Service"
app.Description = ""
app.Action = endpointMonitor.Main(Version)
err := app.Run(os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}
package app
import (
"fmt"
"os"
"strings"
"time"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/urfave/cli"
)
type ProviderConfig struct {
Name string
Url string
}
const (
ProvidersFlagName = "providers"
CheckIntervalFlagName = "check-interval"
CheckDurationFlagName = "check-duration"
)
func CLIFlags(envPrefix string) []cli.Flag {
flags := []cli.Flag{
cli.StringSliceFlag{
Name: ProvidersFlagName,
Usage: "List of providers",
Required: true,
EnvVar: opservice.PrefixEnvVar(envPrefix, "PROVIDERS"),
},
cli.DurationFlag{
Name: CheckIntervalFlagName,
Usage: "Check interval duration",
Value: 5 * time.Minute,
EnvVar: opservice.PrefixEnvVar(envPrefix, "CHECK_INTERVAL"),
},
cli.DurationFlag{
Name: CheckDurationFlagName,
Usage: "Check duration",
Value: 4 * time.Minute,
EnvVar: opservice.PrefixEnvVar(envPrefix, "CHECK_DURATION"),
},
}
flags = append(flags, opmetrics.CLIFlags(envPrefix)...)
flags = append(flags, oplog.CLIFlags(envPrefix)...)
return flags
}
type Config struct {
Providers []string
CheckInterval time.Duration
CheckDuration time.Duration
LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig
}
func (c Config) Check() error {
if c.CheckDuration >= c.CheckInterval {
return fmt.Errorf("%s must be less than %s", CheckDurationFlagName, CheckIntervalFlagName)
}
if err := c.LogConfig.Check(); err != nil {
return err
}
if err := c.MetricsConfig.Check(); err != nil {
return err
}
return nil
}
func NewConfig(ctx *cli.Context) Config {
return Config{
Providers: ctx.GlobalStringSlice(ProvidersFlagName),
CheckInterval: ctx.GlobalDuration(CheckIntervalFlagName),
CheckDuration: ctx.GlobalDuration(CheckDurationFlagName),
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
}
}
// GetProviderConfigs fetches endpoint provider configurations from the environment
// Each provider should have a corresponding env var with the url, ex: PROVIDER1_URL=<provider-url>
func (c Config) GetProviderConfigs() []ProviderConfig {
result := make([]ProviderConfig, 0)
for _, provider := range c.Providers {
envKey := fmt.Sprintf("ENDPOINT_MONITOR_%s_URL", strings.ToUpper(provider))
url := os.Getenv(envKey)
if url == "" {
panic(fmt.Sprintf("%s is not set", envKey))
}
result = append(result, ProviderConfig{Name: provider, Url: url})
}
return result
}
package app
import (
"context"
"fmt"
"strings"
"time"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/l2geth/core/types"
"github.com/ethereum-optimism/optimism/l2geth/ethclient"
)
var (
MetricWsSubscribeStatus = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "ws_subscribe_status",
Help: "eth_subscribe over websocket check status"},
[]string{"status", "provider", "error"},
)
)
func Main(version string) func(cliCtx *cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(cfg.LogConfig)
endpointMonitor := NewEndpointMonitor(cfg, l)
l.Info(fmt.Sprintf("starting endpoint monitor with checkInterval=%s checkDuration=%s", cfg.CheckInterval, cfg.CheckDuration))
endpointMonitor.Start()
ctx := context.Background()
registry := opmetrics.NewRegistry()
registry.MustRegister(MetricWsSubscribeStatus)
metricsCfg := cfg.MetricsConfig
l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil {
l.Error("error starting metrics server", err)
return err
}
return nil
}
}
type EndpointMonitor struct {
cfg Config
logger log.Logger
}
func NewEndpointMonitor(cfg Config, l log.Logger) EndpointMonitor {
return EndpointMonitor{cfg: cfg, logger: l}
}
func (e EndpointMonitor) Start() {
for _, providerConfig := range e.cfg.GetProviderConfigs() {
go e.runWebsocketCheckLoop(providerConfig, e.cfg.CheckInterval, e.cfg.CheckDuration)
}
}
// getWrappingErrorMsg returns the most recently wrapped error message
// it's used in this case to get the error type reported by runSubscribeCallCheck
func getWrappingErrorMsg(err error) string {
cause := errors.Cause(err)
return strings.TrimSuffix(err.Error(), fmt.Sprintf(": %s", cause.Error()))
}
// runWebsocketCheckLoop runs subscribe call checks every checkInterval and reports status metrics to prometheus
func (e EndpointMonitor) runWebsocketCheckLoop(p ProviderConfig, checkInterval, checkDuration time.Duration) {
ticker := time.NewTicker(checkInterval)
defer ticker.Stop()
for {
e.logger.Info("running websocket check", "provider", p.Name)
err := e.runWebsocketCheck(p, checkDuration)
if err != nil {
errType := getWrappingErrorMsg(err)
MetricWsSubscribeStatus.With(prometheus.Labels{"provider": p.Name, "status": "error", "error": errType}).Inc()
e.logger.Error("finished websocket check", "provider", p.Name, "error", errType)
} else {
MetricWsSubscribeStatus.With(prometheus.Labels{"provider": p.Name, "status": "success", "error": ""}).Inc()
e.logger.Info("finished websocket check", "provider", p.Name)
}
<-ticker.C
}
}
// runWebsocketCheck creates a client and subscribes to blockchain head notifications and returns any errors encountered for reporting
func (e EndpointMonitor) runWebsocketCheck(p ProviderConfig, duration time.Duration) error {
client, err := ethclient.Dial(p.Url)
if err != nil {
return errors.Wrap(err, "dial")
}
defer client.Close()
headers := make(chan *types.Header)
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
return errors.Wrap(err, "eth_subscribe_failed")
}
receivedData := false
ticker := time.NewTicker(duration)
defer ticker.Stop()
for {
select {
case <-ticker.C:
sub.Unsubscribe()
if !receivedData {
return errors.New("nodata")
}
return nil
case err := <-sub.Err():
return errors.Wrap(err, "read")
case header := <-headers:
e.logger.Debug(header.Hash().Hex(), "provider", p.Name)
receivedData = true
}
}
}
module github.com/ethereum-optimism/optimism/endpoint-monitor
go 1.18
require (
github.com/ethereum-optimism/optimism/l2geth v0.0.0-20220923210602-7121648c1f26
github.com/ethereum-optimism/optimism/op-service v0.8.8
github.com/ethereum/go-ethereum v1.10.23
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
github.com/urfave/cli v1.22.9
)
require (
github.com/VictoriaMetrics/fastcache v1.9.0 // indirect
github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/elastic/gosigar v0.12.0 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/sys v0.0.0-20220701225701-179beb0bd1a1 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
)
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "@eth-optimism/endpoint-monitor",
"version": "0.0.0",
"private": true,
"dependencies": {}
}
...@@ -3,6 +3,7 @@ go 1.18 ...@@ -3,6 +3,7 @@ go 1.18
use ( use (
./batch-submitter ./batch-submitter
./bss-core ./bss-core
./endpoint-monitor
./gas-oracle ./gas-oracle
./indexer ./indexer
./l2geth ./l2geth
......
...@@ -7,6 +7,10 @@ LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) ...@@ -7,6 +7,10 @@ LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION) LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)" LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
# Note: Requires legacy contracts to be built before
# running and binding-related Make targets.
SCC_ABI_ARTIFACT = ../packages/contracts/artifacts/contracts/L1/rollup/StateCommitmentChain.sol/StateCommitmentChain.json
indexer: indexer:
env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/indexer env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/indexer
...@@ -19,8 +23,28 @@ test: ...@@ -19,8 +23,28 @@ test:
lint: lint:
golangci-lint run ./... golangci-lint run ./...
bindings: bindings-scc
bindings-scc:
$(eval temp := $(shell mktemp))
cat $(SCC_ABI_ARTIFACT) \
| jq -r .bytecode > $(temp)
cat $(SCC_ABI_ARTIFACT) \
| jq .abi \
| abigen --pkg scc \
--abi - \
--out bindings/scc/statecommitmentchain.go \
--type StateCommitmentChain \
--bin $(temp)
rm $(temp)
.PHONY: \ .PHONY: \
indexer \ indexer \
bindings \
bindings-scc \
clean \ clean \
test \ test \
lint lint
This diff is collapsed.
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -20,15 +21,8 @@ var ( ...@@ -20,15 +21,8 @@ var (
type Config struct { type Config struct {
/* Required Params */ /* Required Params */
// BuildEnv identifies the environment this binary is intended for, i.e.
// production, development, etc.
BuildEnv string
// EthNetworkName identifies the intended Ethereum network.
EthNetworkName string
// ChainID identifies the chain being indexed. // ChainID identifies the chain being indexed.
ChainID int64 ChainID uint64
// L1EthRpc is the HTTP provider URL for L1. // L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string L1EthRpc string
...@@ -36,8 +30,8 @@ type Config struct { ...@@ -36,8 +30,8 @@ type Config struct {
// L2EthRpc is the HTTP provider URL for L1. // L2EthRpc is the HTTP provider URL for L1.
L2EthRpc string L2EthRpc string
// L2GenesisBlockHash is the l2 genesis block hash. // L1AddressManagerAddress is the address of the address manager for L1.
L2GenesisBlockHash string L1AddressManagerAddress string
// PollInterval is the delay between querying L2 for more transaction // PollInterval is the delay between querying L2 for more transaction
// and creating a new batch. // and creating a new batch.
...@@ -68,22 +62,8 @@ type Config struct { ...@@ -68,22 +62,8 @@ type Config struct {
// are printed using JSON. // are printed using JSON.
LogTerminal bool LogTerminal bool
// SentryEnable if true, logs any error messages to sentry. SentryDsn // L1StartBlockNumber is the block number to start indexing L1 from.
// must also be set if SentryEnable is true. L1StartBlockNumber uint64
SentryEnable bool
// SentryDsn is the sentry Data Source Name.
SentryDsn string
// SentryTraceRate the frequency with which Sentry should flush buffered
// events.
SentryTraceRate time.Duration
// StartBlockNumber is the block number to start indexing from.
StartBlockNumber uint64
// StartBlockHash is the block hash to start indexing from.
StartBlockHash string
// ConfDepth is the number of confirmations after which headers are // ConfDepth is the number of confirmations after which headers are
// considered confirmed. // considered confirmed.
...@@ -111,6 +91,13 @@ type Config struct { ...@@ -111,6 +91,13 @@ type Config struct {
// DisableIndexer enables/disables the indexer. // DisableIndexer enables/disables the indexer.
DisableIndexer bool DisableIndexer bool
// Bedrock enabled Bedrock indexing.
Bedrock bool
BedrockL1StandardBridgeAddress common.Address
BedrockOptimismPortalAddress common.Address
} }
// NewConfig parses the Config from the provided flags or environment variables. // NewConfig parses the Config from the provided flags or environment variables.
...@@ -118,33 +105,30 @@ type Config struct { ...@@ -118,33 +105,30 @@ type Config struct {
func NewConfig(ctx *cli.Context) (Config, error) { func NewConfig(ctx *cli.Context) (Config, error) {
cfg := Config{ cfg := Config{
/* Required Flags */ /* Required Flags */
BuildEnv: ctx.GlobalString(flags.BuildEnvFlag.Name), ChainID: ctx.GlobalUint64(flags.ChainIDFlag.Name),
EthNetworkName: ctx.GlobalString(flags.EthNetworkNameFlag.Name), L1EthRpc: ctx.GlobalString(flags.L1EthRPCFlag.Name),
ChainID: ctx.GlobalInt64(flags.ChainIDFlag.Name), L2EthRpc: ctx.GlobalString(flags.L2EthRPCFlag.Name),
L1EthRpc: ctx.GlobalString(flags.L1EthRPCFlag.Name), L1AddressManagerAddress: ctx.GlobalString(flags.L1AddressManagerAddressFlag.Name),
L2EthRpc: ctx.GlobalString(flags.L2EthRPCFlag.Name), DBHost: ctx.GlobalString(flags.DBHostFlag.Name),
L2GenesisBlockHash: ctx.GlobalString(flags.L2GenesisBlockHashFlag.Name), DBPort: ctx.GlobalUint64(flags.DBPortFlag.Name),
DBHost: ctx.GlobalString(flags.DBHostFlag.Name), DBUser: ctx.GlobalString(flags.DBUserFlag.Name),
DBPort: ctx.GlobalUint64(flags.DBPortFlag.Name), DBPassword: ctx.GlobalString(flags.DBPasswordFlag.Name),
DBUser: ctx.GlobalString(flags.DBUserFlag.Name), DBName: ctx.GlobalString(flags.DBNameFlag.Name),
DBPassword: ctx.GlobalString(flags.DBPasswordFlag.Name),
DBName: ctx.GlobalString(flags.DBNameFlag.Name),
/* Optional Flags */ /* Optional Flags */
DisableIndexer: ctx.GlobalBool(flags.DisableIndexer.Name), Bedrock: ctx.GlobalBool(flags.BedrockFlag.Name),
LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name), BedrockL1StandardBridgeAddress: common.HexToAddress(ctx.GlobalString(flags.BedrockL1StandardBridgeAddress.Name)),
LogTerminal: ctx.GlobalBool(flags.LogTerminalFlag.Name), BedrockOptimismPortalAddress: common.HexToAddress(ctx.GlobalString(flags.BedrockOptimismPortalAddress.Name)),
SentryEnable: ctx.GlobalBool(flags.SentryEnableFlag.Name), DisableIndexer: ctx.GlobalBool(flags.DisableIndexer.Name),
SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name), LogLevel: ctx.GlobalString(flags.LogLevelFlag.Name),
SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name), LogTerminal: ctx.GlobalBool(flags.LogTerminalFlag.Name),
StartBlockNumber: ctx.GlobalUint64(flags.StartBlockNumberFlag.Name), L1StartBlockNumber: ctx.GlobalUint64(flags.L1StartBlockNumberFlag.Name),
StartBlockHash: ctx.GlobalString(flags.StartBlockHashFlag.Name), ConfDepth: ctx.GlobalUint64(flags.ConfDepthFlag.Name),
ConfDepth: ctx.GlobalUint64(flags.ConfDepthFlag.Name), MaxHeaderBatchSize: ctx.GlobalUint64(flags.MaxHeaderBatchSizeFlag.Name),
MaxHeaderBatchSize: ctx.GlobalUint64(flags.MaxHeaderBatchSizeFlag.Name), MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name),
MetricsServerEnable: ctx.GlobalBool(flags.MetricsServerEnableFlag.Name), RESTHostname: ctx.GlobalString(flags.RESTHostnameFlag.Name),
RESTHostname: ctx.GlobalString(flags.RESTHostnameFlag.Name), RESTPort: ctx.GlobalUint64(flags.RESTPortFlag.Name),
RESTPort: ctx.GlobalUint64(flags.RESTPortFlag.Name), MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name),
MetricsHostname: ctx.GlobalString(flags.MetricsHostnameFlag.Name), MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name),
MetricsPort: ctx.GlobalUint64(flags.MetricsPortFlag.Name),
} }
err := ValidateConfig(&cfg) err := ValidateConfig(&cfg)
...@@ -168,9 +152,8 @@ func ValidateConfig(cfg *Config) error { ...@@ -168,9 +152,8 @@ func ValidateConfig(cfg *Config) error {
return err return err
} }
// Ensure the Sentry Data Source Name is set when using Sentry. if cfg.Bedrock && (cfg.BedrockL1StandardBridgeAddress == common.Address{} || cfg.BedrockOptimismPortalAddress == common.Address{}) {
if cfg.SentryEnable && cfg.SentryDsn == "" { return errors.New("must specify l1 standard bridge and optimism portal addresses in bedrock mode")
return ErrSentryDSNNotSet
} }
return nil return nil
......
This diff is collapsed.
package db package db
import "github.com/ethereum/go-ethereum/common" import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common"
)
var ETHL1Address common.Address
// ETHL1Token is a placeholder token for differentiating ETH transactions from // ETHL1Token is a placeholder token for differentiating ETH transactions from
// ERC20 transactions on L1. // ERC20 transactions on L1.
var ETHL1Token = &Token{ var ETHL1Token = &Token{
Address: "0x0000000000000000000000000000000000000000", Address: ETHL1Address.String(),
Name: "Ethereum", Name: "Ethereum",
Symbol: "ETH", Symbol: "ETH",
Decimals: 18, Decimals: 18,
} }
// ETHL2Address is a placeholder address for differentiating ETH transactions
// from ERC20 transactions on L2.
var ETHL2Address = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000")
// ETHL2Token is a placeholder token for differentiating ETH transactions from // ETHL2Token is a placeholder token for differentiating ETH transactions from
// ERC20 transactions on L2. // ERC20 transactions on L2.
var ETHL2Token = &Token{ var ETHL2Token = &Token{
Address: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", Address: predeploys.LegacyERC20ETH,
Name: "Ethereum", Name: "Ethereum",
Symbol: "ETH", Symbol: "ETH",
Decimals: 18, Decimals: 18,
......
...@@ -6,12 +6,12 @@ import ( ...@@ -6,12 +6,12 @@ import (
// IndexedL1Block contains the L1 block including the deposits in it. // IndexedL1Block contains the L1 block including the deposits in it.
type IndexedL1Block struct { type IndexedL1Block struct {
Hash common.Hash Hash common.Hash
ParentHash common.Hash ParentHash common.Hash
Number uint64 Number uint64
Timestamp uint64 Timestamp uint64
Deposits []Deposit Deposits []Deposit
Withdrawals []Withdrawal FinalizedWithdrawals []FinalizedWithdrawal
} }
// String returns the block hash for the indexed l1 block. // String returns the block hash for the indexed l1 block.
...@@ -25,7 +25,6 @@ type IndexedL2Block struct { ...@@ -25,7 +25,6 @@ type IndexedL2Block struct {
ParentHash common.Hash ParentHash common.Hash
Number uint64 Number uint64
Timestamp uint64 Timestamp uint64
Deposits []Deposit
Withdrawals []Withdrawal Withdrawals []Withdrawal
} }
......
...@@ -28,10 +28,8 @@ CREATE TABLE IF NOT EXISTS deposits ( ...@@ -28,10 +28,8 @@ CREATE TABLE IF NOT EXISTS deposits (
amount VARCHAR NOT NULL, amount VARCHAR NOT NULL,
data BYTEA NOT NULL, data BYTEA NOT NULL,
log_index INTEGER NOT NULL, log_index INTEGER NOT NULL,
l1_block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash), block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash),
l2_block_hash VARCHAR REFERENCES l2_blocks(hash), tx_hash VARCHAR NOT NULL
tx_hash VARCHAR NOT NULL,
failed BOOLEAN NOT NULL DEFAULT false
) )
` `
...@@ -53,6 +51,20 @@ CREATE TABLE IF NOT EXISTS l2_tokens ( ...@@ -53,6 +51,20 @@ CREATE TABLE IF NOT EXISTS l2_tokens (
) )
` `
const createStateBatchesTable = `
CREATE TABLE IF NOT EXISTS state_batches (
index INTEGER NOT NULL PRIMARY KEY,
root VARCHAR NOT NULL,
size INTEGER NOT NULL,
prev_total INTEGER NOT NULL,
extra_data BYTEA NOT NULL,
block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash)
);
CREATE INDEX IF NOT EXISTS state_batches_block_hash ON state_batches(block_hash);
CREATE INDEX IF NOT EXISTS state_batches_size ON state_batches(size);
CREATE INDEX IF NOT EXISTS state_batches_prev_total ON state_batches(prev_total);
`
const createWithdrawalsTable = ` const createWithdrawalsTable = `
CREATE TABLE IF NOT EXISTS withdrawals ( CREATE TABLE IF NOT EXISTS withdrawals (
guid VARCHAR PRIMARY KEY NOT NULL, guid VARCHAR PRIMARY KEY NOT NULL,
...@@ -63,9 +75,9 @@ CREATE TABLE IF NOT EXISTS withdrawals ( ...@@ -63,9 +75,9 @@ CREATE TABLE IF NOT EXISTS withdrawals (
amount VARCHAR NOT NULL, amount VARCHAR NOT NULL,
data BYTEA NOT NULL, data BYTEA NOT NULL,
log_index INTEGER NOT NULL, log_index INTEGER NOT NULL,
l1_block_hash VARCHAR REFERENCES l1_blocks(hash), block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash),
l2_block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash), tx_hash VARCHAR NOT NULL,
tx_hash VARCHAR NOT NULL state_batch INTEGER REFERENCES state_batches(index)
) )
` `
...@@ -110,15 +122,25 @@ CREATE TABLE IF NOT EXISTS airdrops ( ...@@ -110,15 +122,25 @@ CREATE TABLE IF NOT EXISTS airdrops (
) )
` `
const updateWithdrawalsTable = `
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_hash VARCHAR NULL;
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_tx_hash VARCHAR NULL;
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_log_index INTEGER NULL;
ALTER TABLE withdrawals ADD COLUMN IF NOT EXISTS br_withdrawal_finalized_success BOOLEAN NULL;
CREATE INDEX IF NOT EXISTS withdrawals_br_withdrawal_hash ON withdrawals(br_withdrawal_hash);
`
var schema = []string{ var schema = []string{
createL1BlocksTable, createL1BlocksTable,
createL2BlocksTable, createL2BlocksTable,
createL1TokensTable, createL1TokensTable,
createL2TokensTable, createL2TokensTable,
createStateBatchesTable,
insertETHL1Token, insertETHL1Token,
insertETHL2Token, insertETHL2Token,
createDepositsTable, createDepositsTable,
createWithdrawalsTable, createWithdrawalsTable,
createL1L2NumberIndex, createL1L2NumberIndex,
createAirdropsTable, createAirdropsTable,
updateWithdrawalsTable,
} }
package db
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// StateBatch is the state batch containing merkle root of the withdrawals
// periodically written to L1.
type StateBatch struct {
Index *big.Int
Root common.Hash
Size *big.Int
PrevTotal *big.Int
ExtraData []byte
BlockHash common.Hash
}
// StateBatchJSON contains StateBatch data suitable for JSON serialization.
type StateBatchJSON struct {
Index uint64 `json:"index"`
Root string `json:"root"`
Size uint64 `json:"size"`
PrevTotal uint64 `json:"prevTotal"`
ExtraData []byte `json:"extraData"`
BlockHash string `json:"blockHash"`
BlockNumber uint64 `json:"blockNumber"`
BlockTimestamp uint64 `json:"blockTimestamp"`
}
...@@ -17,6 +17,7 @@ type Withdrawal struct { ...@@ -17,6 +17,7 @@ type Withdrawal struct {
Amount *big.Int Amount *big.Int
Data []byte Data []byte
LogIndex uint LogIndex uint
BedrockHash *common.Hash
} }
// String returns the tx hash for the withdrawal. // String returns the tx hash for the withdrawal.
...@@ -26,17 +27,57 @@ func (w Withdrawal) String() string { ...@@ -26,17 +27,57 @@ func (w Withdrawal) String() string {
// WithdrawalJSON contains Withdrawal data suitable for JSON serialization. // WithdrawalJSON contains Withdrawal data suitable for JSON serialization.
type WithdrawalJSON struct { type WithdrawalJSON struct {
GUID string `json:"guid"` GUID string `json:"guid"`
FromAddress string `json:"from"` FromAddress string `json:"from"`
ToAddress string `json:"to"` ToAddress string `json:"to"`
L1Token string `json:"l1Token"` L1Token string `json:"l1Token"`
L2Token *Token `json:"l2Token"` L2Token *Token `json:"l2Token"`
Amount string `json:"amount"` Amount string `json:"amount"`
Data []byte `json:"data"` Data []byte `json:"data"`
LogIndex uint64 `json:"logIndex"` LogIndex uint64 `json:"logIndex"`
L1BlockNumber uint64 `json:"l1BlockNumber"` BlockNumber uint64 `json:"blockNumber"`
L1BlockTimestamp string `json:"l1BlockTimestamp"` BlockTimestamp string `json:"blockTimestamp"`
L2BlockNumber uint64 `json:"l2BlockNumber"` TxHash string `json:"transactionHash"`
L2BlockTimestamp string `json:"l2BlockTimestamp"` Batch *StateBatchJSON `json:"batch"`
TxHash string `json:"transactionHash"` BedrockWithdrawalHash *string `json:"bedrockWithdrawalHash"`
BedrockFinalizedTxHash *string `json:"bedrockFinalizedTxHash"`
BedrockFinalizedLogIndex *int `json:"bedrockFinalizedLogIndex"`
BedrockFinalizedSuccess *bool `json:"bedrockFinalizedSuccess"`
}
type FinalizationState int
const (
FinalizationStateAny FinalizationState = iota
FinalizationStateFinalized
FinalizationStateUnfinalized
)
func ParseFinalizationState(in string) FinalizationState {
switch in {
case "true":
return FinalizationStateFinalized
case "false":
return FinalizationStateUnfinalized
default:
return FinalizationStateAny
}
}
func (f FinalizationState) SQL() string {
switch f {
case FinalizationStateFinalized:
return "AND withdrawals.br_withdrawal_finalized_tx_hash IS NOT NULL"
case FinalizationStateUnfinalized:
return "AND withdrawals.br_withdrawal_finalized_tx_hash IS NULL"
}
return ""
}
type FinalizedWithdrawal struct {
WithdrawalHash common.Hash
TxHash common.Hash
Success bool
LogIndex uint
} }
...@@ -46,11 +46,11 @@ var ( ...@@ -46,11 +46,11 @@ var (
Required: true, Required: true,
EnvVar: prefixEnvVar("L2_ETH_RPC"), EnvVar: prefixEnvVar("L2_ETH_RPC"),
} }
L2GenesisBlockHashFlag = cli.StringFlag{ L1AddressManagerAddressFlag = cli.StringFlag{
Name: "l2-genesis-block-hash", Name: "l1-address-manager-address",
Usage: "Genesis block hash of the L2 chain", Usage: "Address of the L1 address manager",
Required: true, Required: true,
EnvVar: prefixEnvVar("L2_GENESIS_BLOCK_HASH"), EnvVar: prefixEnvVar("L1_ADDRESS_MANAGER_ADDRESS"),
} }
DBHostFlag = cli.StringFlag{ DBHostFlag = cli.StringFlag{
Name: "db-host", Name: "db-host",
...@@ -83,6 +83,23 @@ var ( ...@@ -83,6 +83,23 @@ var (
EnvVar: prefixEnvVar("DB_NAME"), EnvVar: prefixEnvVar("DB_NAME"),
} }
/* Bedrock Flags */
BedrockFlag = cli.BoolFlag{
Name: "bedrock",
Usage: "Whether or not this indexer should operate in Bedrock mode",
EnvVar: prefixEnvVar("BEDROCK"),
}
BedrockL1StandardBridgeAddress = cli.BoolFlag{
Name: "bedrock.l1-standard-bridge-address",
Usage: "Address of the L1 standard bridge",
EnvVar: prefixEnvVar("BEDROCK_L1_STANDARD_BRIDGE"),
}
BedrockOptimismPortalAddress = cli.BoolFlag{
Name: "bedrock.portal-address",
Usage: "Address of the portal",
EnvVar: prefixEnvVar("BEDROCK_OPTIMISM_PORTAL"),
}
/* Optional Flags */ /* Optional Flags */
DisableIndexer = cli.BoolFlag{ DisableIndexer = cli.BoolFlag{
...@@ -120,18 +137,12 @@ var ( ...@@ -120,18 +137,12 @@ var (
Value: 50 * time.Millisecond, Value: 50 * time.Millisecond,
EnvVar: prefixEnvVar("SENTRY_TRACE_RATE"), EnvVar: prefixEnvVar("SENTRY_TRACE_RATE"),
} }
StartBlockNumberFlag = cli.Uint64Flag{ L1StartBlockNumberFlag = cli.Uint64Flag{
Name: "start-block-number", Name: "start-block-number",
Usage: "The block number to start indexing from. Must be use together with start block hash", Usage: "The block number to start indexing from. Must be use together with start block hash",
Value: 0, Value: 0,
EnvVar: prefixEnvVar("START_BLOCK_NUMBER"), EnvVar: prefixEnvVar("START_BLOCK_NUMBER"),
} }
StartBlockHashFlag = cli.StringFlag{
Name: "start-block-hash",
Usage: "The block hash to start indexing from. Must be use together with start block number",
Value: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
EnvVar: prefixEnvVar("START_BLOCK_HASH"),
}
ConfDepthFlag = cli.Uint64Flag{ ConfDepthFlag = cli.Uint64Flag{
Name: "conf-depth", Name: "conf-depth",
Usage: "The number of confirmations after which headers are considered confirmed", Usage: "The number of confirmations after which headers are considered confirmed",
...@@ -181,7 +192,7 @@ var requiredFlags = []cli.Flag{ ...@@ -181,7 +192,7 @@ var requiredFlags = []cli.Flag{
ChainIDFlag, ChainIDFlag,
L1EthRPCFlag, L1EthRPCFlag,
L2EthRPCFlag, L2EthRPCFlag,
L2GenesisBlockHashFlag, L1AddressManagerAddressFlag,
DBHostFlag, DBHostFlag,
DBPortFlag, DBPortFlag,
DBUserFlag, DBUserFlag,
...@@ -190,6 +201,9 @@ var requiredFlags = []cli.Flag{ ...@@ -190,6 +201,9 @@ var requiredFlags = []cli.Flag{
} }
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
BedrockFlag,
BedrockL1StandardBridgeAddress,
BedrockOptimismPortalAddress,
DisableIndexer, DisableIndexer,
LogLevelFlag, LogLevelFlag,
LogTerminalFlag, LogTerminalFlag,
...@@ -198,8 +212,7 @@ var optionalFlags = []cli.Flag{ ...@@ -198,8 +212,7 @@ var optionalFlags = []cli.Flag{
SentryTraceRateFlag, SentryTraceRateFlag,
ConfDepthFlag, ConfDepthFlag,
MaxHeaderBatchSizeFlag, MaxHeaderBatchSizeFlag,
StartBlockNumberFlag, L1StartBlockNumberFlag,
StartBlockHashFlag,
RESTHostnameFlag, RESTHostnameFlag,
RESTPortFlag, RESTPortFlag,
MetricsServerEnableFlag, MetricsServerEnableFlag,
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/ethereum-optimism/optimism/indexer/services" "github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/indexer/metrics" "github.com/ethereum-optimism/optimism/indexer/metrics"
"github.com/ethereum-optimism/optimism/indexer/server" "github.com/ethereum-optimism/optimism/indexer/server"
...@@ -22,7 +23,6 @@ import ( ...@@ -22,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
sentry "github.com/getsentry/sentry-go"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -44,15 +44,9 @@ func Main(gitVersion string) func(ctx *cli.Context) error { ...@@ -44,15 +44,9 @@ func Main(gitVersion string) func(ctx *cli.Context) error {
return err return err
} }
// The call to defer is done here so that any errors logged from
// this point on are posted to Sentry before exiting.
if cfg.SentryEnable {
defer sentry.Flush(2 * time.Second)
}
log.Info("Initializing indexer") log.Info("Initializing indexer")
indexer, err := NewIndexer(cfg, gitVersion) indexer, err := NewIndexer(cfg)
if err != nil { if err != nil {
log.Error("Unable to create indexer", "error", err) log.Error("Unable to create indexer", "error", err)
return err return err
...@@ -87,32 +81,18 @@ type Indexer struct { ...@@ -87,32 +81,18 @@ type Indexer struct {
router *mux.Router router *mux.Router
metrics *metrics.Metrics metrics *metrics.Metrics
db *database.Database
server *http.Server
} }
// NewIndexer initializes the Indexer, gathering any resources // NewIndexer initializes the Indexer, gathering any resources
// that will be needed by the TxIndexer and StateIndexer // that will be needed by the TxIndexer and StateIndexer
// sub-services. // sub-services.
func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { func NewIndexer(cfg Config) (*Indexer, error) {
ctx := context.Background() ctx := context.Background()
// Set up our logging. If Sentry is enabled, we will use our custom
// log handler that logs to stdout and forwards any error messages to
// Sentry for collection. Otherwise, logs will only be posted to stdout.
var logHandler log.Handler var logHandler log.Handler
if cfg.SentryEnable { if cfg.LogTerminal {
err := sentry.Init(sentry.ClientOptions{
Dsn: cfg.SentryDsn,
Environment: cfg.EthNetworkName,
Release: "indexer@" + gitVersion,
TracesSampleRate: traceRateToFloat64(cfg.SentryTraceRate),
Debug: false,
})
if err != nil {
return nil, err
}
logHandler = SentryStreamHandler(os.Stdout, log.JSONFormat())
} else if cfg.LogTerminal {
logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true)) logHandler = log.StreamHandler(os.Stdout, log.TerminalFormat(true))
} else { } else {
logHandler = log.StreamHandler(os.Stdout, log.JSONFormat()) logHandler = log.StreamHandler(os.Stdout, log.JSONFormat())
...@@ -149,8 +129,11 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -149,8 +129,11 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
log.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort) log.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort)
} }
dsn := fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=disable", dsn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable",
cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBName) cfg.DBHost, cfg.DBPort, cfg.DBName)
if cfg.DBUser != "" {
dsn += fmt.Sprintf(" user=%s", cfg.DBUser)
}
if cfg.DBPassword != "" { if cfg.DBPassword != "" {
dsn += fmt.Sprintf(" password=%s", cfg.DBPassword) dsn += fmt.Sprintf(" password=%s", cfg.DBPassword)
} }
...@@ -159,17 +142,32 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -159,17 +142,32 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
return nil, err return nil, err
} }
var addrManager services.AddressManager
if cfg.Bedrock {
addrManager, err = services.NewBedrockAddresses(
l1Client,
cfg.BedrockL1StandardBridgeAddress,
cfg.BedrockOptimismPortalAddress,
)
} else {
addrManager, err = services.NewLegacyAddresses(l1Client, common.HexToAddress(cfg.L1AddressManagerAddress))
}
if err != nil {
return nil, err
}
l1IndexingService, err := l1.NewService(l1.ServiceConfig{ l1IndexingService, err := l1.NewService(l1.ServiceConfig{
Context: ctx, Context: ctx,
Metrics: m, Metrics: m,
L1Client: l1Client, L1Client: l1Client,
RawL1Client: rawl1Client, RawL1Client: rawl1Client,
ChainID: big.NewInt(cfg.ChainID), ChainID: new(big.Int).SetUint64(cfg.ChainID),
AddressManager: addrManager,
DB: db, DB: db,
ConfDepth: cfg.ConfDepth, ConfDepth: cfg.ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize, MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: cfg.StartBlockNumber, StartBlockNumber: cfg.L1StartBlockNumber,
StartBlockHash: cfg.StartBlockHash, Bedrock: cfg.Bedrock,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -184,7 +182,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -184,7 +182,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
ConfDepth: cfg.ConfDepth, ConfDepth: cfg.ConfDepth,
MaxHeaderBatchSize: cfg.MaxHeaderBatchSize, MaxHeaderBatchSize: cfg.MaxHeaderBatchSize,
StartBlockNumber: uint64(0), StartBlockNumber: uint64(0),
StartBlockHash: cfg.L2GenesisBlockHash, Bedrock: cfg.Bedrock,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -200,6 +198,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) { ...@@ -200,6 +198,7 @@ func NewIndexer(cfg Config, gitVersion string) (*Indexer, error) {
airdropService: services.NewAirdrop(db, m), airdropService: services.NewAirdrop(db, m),
router: mux.NewRouter(), router: mux.NewRouter(),
metrics: m, metrics: m,
db: db,
}, nil }, nil
} }
...@@ -212,7 +211,7 @@ func (b *Indexer) Serve() error { ...@@ -212,7 +211,7 @@ func (b *Indexer) Serve() error {
b.router.HandleFunc("/v1/l1/status", b.l1IndexingService.GetIndexerStatus).Methods("GET") b.router.HandleFunc("/v1/l1/status", b.l1IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/l2/status", b.l2IndexingService.GetIndexerStatus).Methods("GET") b.router.HandleFunc("/v1/l2/status", b.l2IndexingService.GetIndexerStatus).Methods("GET")
b.router.HandleFunc("/v1/deposits/0x{address:[a-fA-F0-9]{40}}", b.l1IndexingService.GetDeposits).Methods("GET") b.router.HandleFunc("/v1/deposits/0x{address:[a-fA-F0-9]{40}}", b.l1IndexingService.GetDeposits).Methods("GET")
b.router.HandleFunc("/v1/withdrawal/0x{hash:[a-fA-F0-9]{64}}", b.l2IndexingService.GetWithdrawalStatus).Methods("GET") b.router.HandleFunc("/v1/withdrawal/0x{hash:[a-fA-F0-9]{64}}", b.l2IndexingService.GetWithdrawalBatch).Methods("GET")
b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET") b.router.HandleFunc("/v1/withdrawals/0x{address:[a-fA-F0-9]{40}}", b.l2IndexingService.GetWithdrawals).Methods("GET")
b.router.HandleFunc("/v1/airdrops/0x{address:[a-fA-F0-9]{40}}", b.airdropService.GetAirdrop) b.router.HandleFunc("/v1/airdrops/0x{address:[a-fA-F0-9]{40}}", b.airdropService.GetAirdrop)
b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { b.router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
...@@ -228,8 +227,27 @@ func (b *Indexer) Serve() error { ...@@ -228,8 +227,27 @@ func (b *Indexer) Serve() error {
port := strconv.FormatUint(b.cfg.RESTPort, 10) port := strconv.FormatUint(b.cfg.RESTPort, 10)
addr := net.JoinHostPort(b.cfg.RESTHostname, port) addr := net.JoinHostPort(b.cfg.RESTHostname, port)
log.Info("indexer REST server listening on", "addr", addr) b.server = &http.Server{
return http.ListenAndServe(addr, middleware(c.Handler(b.router))) Addr: addr,
Handler: middleware(c.Handler(b.router)),
}
errCh := make(chan error, 1)
go func() {
errCh <- b.server.ListenAndServe()
}()
// Capture server startup errors
<-time.After(10 * time.Millisecond)
select {
case err := <-errCh:
return err
default:
log.Info("indexer REST server listening on", "addr", addr)
return nil
}
} }
// Start starts the starts the indexing service on L1 and L2 chains and also // Start starts the starts the indexing service on L1 and L2 chains and also
...@@ -253,6 +271,14 @@ func (b *Indexer) Start() error { ...@@ -253,6 +271,14 @@ func (b *Indexer) Start() error {
// Stop stops the indexing service on L1 and L2 chains. // Stop stops the indexing service on L1 and L2 chains.
func (b *Indexer) Stop() { func (b *Indexer) Stop() {
b.db.Close()
if b.server != nil {
// background context here so it waits for
// conns to close
_ = b.server.Shutdown(context.Background())
}
if !b.cfg.DisableIndexer { if !b.cfg.DisableIndexer {
b.l1IndexingService.Stop() b.l1IndexingService.Stop()
b.l2IndexingService.Stop() b.l2IndexingService.Stop()
...@@ -274,14 +300,3 @@ func dialEthClientWithTimeout(ctx context.Context, url string) ( ...@@ -274,14 +300,3 @@ func dialEthClientWithTimeout(ctx context.Context, url string) (
} }
return ethclient.NewClient(c), c, nil return ethclient.NewClient(c), c, nil
} }
// traceRateToFloat64 converts a time.Duration into a valid float64 for the
// Sentry client. The client only accepts values between 0.0 and 1.0, so this
// method clamps anything greater than 1 second to 1.0.
func traceRateToFloat64(rate time.Duration) float64 {
rate64 := float64(rate) / float64(time.Second)
if rate64 > 1.0 {
rate64 = 1.0
}
return rate64
}
package integration_tests
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"math/big"
"net/http"
"os"
"testing"
"time"
"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services/l1"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
_ "github.com/lib/pq"
)
func TestBedrockIndexer(t *testing.T) {
dbParams := createTestDB(t)
cfg := op_e2e.DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 2
sys, err := cfg.Start()
require.NoError(t, err)
defer sys.Close()
l1Client := sys.Clients["l1"]
l2Client := sys.Clients["sequencer"]
fromAddr := cfg.Secrets.Addresses().Alice
// wait a couple of blocks
require.NoError(t, e2eutils.WaitBlock(e2eutils.TimeoutCtx(t, 30*time.Second), l2Client, 10))
l1SB, err := bindings.NewL1StandardBridge(predeploys.DevL1StandardBridgeAddr, l1Client)
require.NoError(t, err)
l2SB, err := bindings.NewL2StandardBridge(predeploys.L2StandardBridgeAddr, l2Client)
require.NoError(t, err)
portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
require.NoError(t, err)
l1Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L1ChainIDBig())
require.NoError(t, err)
l2Opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Alice, cfg.L2ChainIDBig())
require.NoError(t, err)
idxrCfg := indexer.Config{
ChainID: cfg.DeployConfig.L1ChainID,
L1EthRpc: sys.Nodes["l1"].HTTPEndpoint(),
L2EthRpc: sys.Nodes["sequencer"].HTTPEndpoint(),
PollInterval: time.Second,
DBHost: dbParams.Host,
DBPort: dbParams.Port,
DBUser: dbParams.User,
DBPassword: dbParams.Password,
DBName: dbParams.Name,
LogLevel: "info",
LogTerminal: true,
L1StartBlockNumber: 0,
ConfDepth: 1,
MaxHeaderBatchSize: 2,
RESTHostname: "127.0.0.1",
RESTPort: 7980,
DisableIndexer: false,
Bedrock: true,
BedrockL1StandardBridgeAddress: predeploys.DevL1StandardBridgeAddr,
BedrockOptimismPortalAddress: predeploys.DevOptimismPortalAddr,
}
idxr, err := indexer.NewIndexer(idxrCfg)
require.NoError(t, err)
errCh := make(chan error, 1)
go func() {
errCh <- idxr.Start()
}()
t.Cleanup(func() {
idxr.Stop()
require.NoError(t, <-errCh)
})
makeURL := func(path string) string {
return fmt.Sprintf("http://%s:%d/%s", idxrCfg.RESTHostname, idxrCfg.RESTPort, path)
}
t.Run("deposit ETH", func(t *testing.T) {
l1Opts.Value = big.NewInt(params.Ether)
depTx, err := l1SB.DepositETH(l1Opts, 200_000, nil)
require.NoError(t, err)
depReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, 10*time.Second), l1Client, depTx.Hash())
require.NoError(t, err)
require.Greaterf(t, len(depReceipt.Logs), 0, "must have logs")
var l2Hash common.Hash
for _, eLog := range depReceipt.Logs {
if len(eLog.Topics) == 0 || eLog.Topics[0] != derive.DepositEventABIHash {
continue
}
depLog, err := derive.UnmarshalDepositLogEvent(eLog)
require.NoError(t, err)
tx := types.NewTx(depLog)
l2Hash = tx.Hash()
}
require.NotEqual(t, common.Hash{}, l2Hash)
_, err = e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, 15*time.Second), l2Client, l2Hash)
require.NoError(t, err)
// Poll for indexer deposit
var depPage *db.PaginatedDeposits
require.NoError(t, e2eutils.WaitFor(e2eutils.TimeoutCtx(t, 30*time.Second), 100*time.Millisecond, func() (bool, error) {
res := new(db.PaginatedDeposits)
err := getJSON(makeURL(fmt.Sprintf("v1/deposits/%s", fromAddr)), res)
if err != nil {
return false, err
}
if len(res.Deposits) == 0 {
return false, nil
}
depPage = res
return true, nil
}))
// Make sure deposit is what we expect
require.Equal(t, 1, len(depPage.Deposits))
deposit := depPage.Deposits[0]
require.Equal(t, big.NewInt(params.Ether).String(), deposit.Amount)
require.Equal(t, depTx.Hash().String(), deposit.TxHash)
require.Equal(t, depReceipt.BlockNumber.Uint64(), deposit.BlockNumber)
require.Equal(t, fromAddr.String(), deposit.FromAddress)
require.Equal(t, fromAddr.String(), deposit.ToAddress)
require.EqualValues(t, db.ETHL1Token, deposit.L1Token)
require.Equal(t, l1.ZeroAddress.String(), deposit.L2Token)
require.NotEmpty(t, deposit.GUID)
// Perform withdrawal through bridge
l2Opts.Value = big.NewInt(0.5 * params.Ether)
wdTx, err := l2SB.Withdraw(l2Opts, predeploys.LegacyERC20ETHAddr, big.NewInt(0.5*params.Ether), 0, nil)
require.NoError(t, err)
wdReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, 30*time.Second), l2Client, wdTx.Hash())
require.NoError(t, err)
var wdPage *db.PaginatedWithdrawals
require.NoError(t, e2eutils.WaitFor(e2eutils.TimeoutCtx(t, 30*time.Second), 100*time.Millisecond, func() (bool, error) {
res := new(db.PaginatedWithdrawals)
err := getJSON(makeURL(fmt.Sprintf("v1/withdrawals/%s", fromAddr)), res)
if err != nil {
return false, err
}
if len(res.Withdrawals) == 0 {
return false, nil
}
wdPage = res
return true, nil
}))
require.Equal(t, 1, len(wdPage.Withdrawals))
withdrawal := wdPage.Withdrawals[0]
require.Nil(t, withdrawal.BedrockFinalizedTxHash)
require.Equal(t, big.NewInt(0.5*params.Ether).String(), withdrawal.Amount)
require.Equal(t, wdTx.Hash().String(), withdrawal.TxHash)
require.Equal(t, wdReceipt.BlockNumber.Uint64(), withdrawal.BlockNumber)
// use fromaddr twice here because the user is withdrawing
// to themselves
require.Equal(t, fromAddr.String(), withdrawal.FromAddress)
require.Equal(t, fromAddr.String(), withdrawal.ToAddress)
require.EqualValues(t, l1.ZeroAddress.String(), withdrawal.L1Token)
require.Equal(t, db.ETHL2Token, withdrawal.L2Token)
require.NotEmpty(t, withdrawal.GUID)
finBlockNum, err := withdrawals.WaitForFinalizationPeriod(
e2eutils.TimeoutCtx(t, time.Minute),
l1Client,
predeploys.DevOptimismPortalAddr,
wdReceipt.BlockNumber,
)
require.NoError(t, err)
finHeader, err := l2Client.HeaderByNumber(context.Background(), big.NewInt(int64(finBlockNum)))
require.NoError(t, err)
rpcClient, err := rpc.Dial(sys.Nodes["sequencer"].HTTPEndpoint())
require.NoError(t, err)
proofClient := withdrawals.NewClient(rpcClient)
wParams, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofClient, wdTx.Hash(), finHeader)
require.NoError(t, err)
l1Opts.Value = big.NewInt(0)
finTx, err := portal.FinalizeWithdrawalTransaction(
l1Opts,
bindings.TypesWithdrawalTransaction{
Nonce: wParams.Nonce,
Sender: wParams.Sender,
Target: wParams.Target,
Value: wParams.Value,
GasLimit: wParams.GasLimit,
Data: wParams.Data,
},
wParams.BlockNumber,
wParams.OutputRootProof,
wParams.WithdrawalProof,
)
require.NoError(t, err)
finReceipt, err := e2eutils.WaitReceiptOK(e2eutils.TimeoutCtx(t, time.Minute), l1Client, finTx.Hash())
require.NoError(t, err)
wdPage = nil
require.NoError(t, e2eutils.WaitFor(e2eutils.TimeoutCtx(t, 30*time.Second), 100*time.Millisecond, func() (bool, error) {
res := new(db.PaginatedWithdrawals)
err := getJSON(makeURL(fmt.Sprintf("v1/withdrawals/%s", fromAddr)), res)
if err != nil {
return false, err
}
if res.Withdrawals[0].BedrockFinalizedTxHash == nil {
return false, nil
}
wdPage = res
return true, nil
}))
wd := wdPage.Withdrawals[0]
require.Equal(t, finReceipt.TxHash.String(), *wd.BedrockFinalizedTxHash)
require.True(t, *wd.BedrockFinalizedSuccess)
wdPage = new(db.PaginatedWithdrawals)
err = getJSON(makeURL(fmt.Sprintf("v1/withdrawals/%s?finalized=false", fromAddr)), wdPage)
require.NoError(t, err)
require.Equal(t, 0, len(wdPage.Withdrawals))
})
}
type testDBParams struct {
Host string
Port uint64
User string
Password string
Name string
}
func createTestDB(t *testing.T) *testDBParams {
user := os.Getenv("DB_USER")
name := fmt.Sprintf("indexer_test_%d", time.Now().Unix())
dsn := "postgres://"
if user != "" {
dsn += user
dsn += "@"
}
dsn += "localhost:5432?sslmode=disable"
pg, err := sql.Open(
"postgres",
dsn,
)
require.NoError(t, err)
_, err = pg.Exec("CREATE DATABASE " + name)
require.NoError(t, err)
t.Cleanup(func() {
_, err = pg.Exec("DROP DATABASE " + name)
require.NoError(t, err)
pg.Close()
})
return &testDBParams{
Host: "localhost",
Port: 5432,
Name: name,
User: user,
}
}
func getJSON(url string, out interface{}) error {
res, err := http.Get(url)
if err != nil {
return err
}
if res.StatusCode != 200 {
return fmt.Errorf("non-200 status code %d", res.StatusCode)
}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
return dec.Decode(out)
}
...@@ -21,6 +21,8 @@ type Metrics struct { ...@@ -21,6 +21,8 @@ type Metrics struct {
WithdrawalsCount *prometheus.CounterVec WithdrawalsCount *prometheus.CounterVec
StateBatchesCount prometheus.Counter
L1CatchingUp prometheus.Gauge L1CatchingUp prometheus.Gauge
L2CatchingUp prometheus.Gauge L2CatchingUp prometheus.Gauge
...@@ -72,6 +74,12 @@ func NewMetrics(monitoredTokens map[string]string) *Metrics { ...@@ -72,6 +74,12 @@ func NewMetrics(monitoredTokens map[string]string) *Metrics {
"symbol", "symbol",
}), }),
StateBatchesCount: promauto.NewCounter(prometheus.CounterOpts{
Name: "state_batches_count",
Help: "The number of state batches indexed.",
Namespace: metricsNamespace,
}),
L1CatchingUp: promauto.NewGauge(prometheus.GaugeOpts{ L1CatchingUp: promauto.NewGauge(prometheus.GaugeOpts{
Name: "l1_catching_up", Name: "l1_catching_up",
Help: "Whether or not L1 is far behind the chain tip.", Help: "Whether or not L1 is far behind the chain tip.",
...@@ -160,6 +168,10 @@ func (m *Metrics) RecordWithdrawal(addr common.Address) { ...@@ -160,6 +168,10 @@ func (m *Metrics) RecordWithdrawal(addr common.Address) {
m.WithdrawalsCount.WithLabelValues(sym).Inc() m.WithdrawalsCount.WithLabelValues(sym).Inc()
} }
func (m *Metrics) RecordStateBatches(count int) {
m.StateBatchesCount.Add(float64(count))
}
func (m *Metrics) SetL1CatchingUp(state bool) { func (m *Metrics) SetL1CatchingUp(state bool) {
var catchingUp float64 var catchingUp float64
if state { if state {
......
package indexer
import (
"errors"
"io"
"github.com/ethereum/go-ethereum/log"
"github.com/getsentry/sentry-go"
)
var jsonFmt = log.JSONFormat()
// SentryStreamHandler creates a log.Handler that behaves similarly to
// log.StreamHandler, however it writes any log with severity greater than or
// equal to log.LvlError to Sentry. In that case, the passed log.Record is
// encoded using JSON rather than the default terminal output, so that it can be
// captured for debugging in the Sentry dashboard.
func SentryStreamHandler(wr io.Writer, fmtr log.Format) log.Handler {
h := log.FuncHandler(func(r *log.Record) error {
_, err := wr.Write(fmtr.Format(r))
// If this record's severity is log.LvlError or higher,
// serialize the record using JSON and write it to Sentry. We
// also capture the error message separately so that it's easy
// to parse what the error is in the dashboard.
//
// NOTE: The log.Lvl* constants are defined in reverse order of
// their severity, i.e. zero (log.LvlCrit) is the highest
// severity.
if r.Lvl <= log.LvlError {
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("context", jsonFmt.Format(r))
sentry.CaptureException(errors.New(r.Msg))
})
}
return err
})
return log.LazyHandler(log.SyncHandler(h))
}
package services
import (
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type AddressManager interface {
L1StandardBridge() (common.Address, *bindings.L1StandardBridge)
StateCommitmentChain() (common.Address, *scc.StateCommitmentChain)
OptimismPortal() (common.Address, *bindings.OptimismPortal)
}
type LegacyAddresses struct {
l1SB *bindings.L1StandardBridge
l1SBAddr common.Address
scc *scc.StateCommitmentChain
sccAddr common.Address
}
var _ AddressManager = (*LegacyAddresses)(nil)
func NewLegacyAddresses(client bind.ContractBackend, addrMgrAddr common.Address) (AddressManager, error) {
mgr, err := bindings.NewAddressManager(addrMgrAddr, client)
if err != nil {
return nil, err
}
l1SBAddr, err := mgr.GetAddress(nil, "Proxy__OVM_L1StandardBridge")
if err != nil {
return nil, err
}
sccAddr, err := mgr.GetAddress(nil, "StateCommitmentChain")
if err != nil {
return nil, err
}
l1SB, err := bindings.NewL1StandardBridge(l1SBAddr, client)
if err != nil {
return nil, err
}
sccContract, err := scc.NewStateCommitmentChain(sccAddr, client)
if err != nil {
return nil, err
}
return &LegacyAddresses{
l1SB: l1SB,
l1SBAddr: l1SBAddr,
scc: sccContract,
sccAddr: sccAddr,
}, nil
}
func (a *LegacyAddresses) L1StandardBridge() (common.Address, *bindings.L1StandardBridge) {
return a.l1SBAddr, a.l1SB
}
func (a *LegacyAddresses) StateCommitmentChain() (common.Address, *scc.StateCommitmentChain) {
return a.sccAddr, a.scc
}
func (a *LegacyAddresses) OptimismPortal() (common.Address, *bindings.OptimismPortal) {
panic("OptimismPortal not configured on legacy networks - this is a programmer error")
}
type BedrockAddresses struct {
l1SB *bindings.L1StandardBridge
l1SBAddr common.Address
portal *bindings.OptimismPortal
portalAddr common.Address
}
var _ AddressManager = (*BedrockAddresses)(nil)
func NewBedrockAddresses(client bind.ContractBackend, l1SBAddr, portalAddr common.Address) (AddressManager, error) {
l1SB, err := bindings.NewL1StandardBridge(l1SBAddr, client)
if err != nil {
return nil, err
}
portal, err := bindings.NewOptimismPortal(portalAddr, client)
if err != nil {
return nil, err
}
return &BedrockAddresses{
l1SB: l1SB,
l1SBAddr: l1SBAddr,
portal: portal,
portalAddr: portalAddr,
}, nil
}
func (b *BedrockAddresses) L1StandardBridge() (common.Address, *bindings.L1StandardBridge) {
return b.l1SBAddr, b.l1SB
}
func (b *BedrockAddresses) StateCommitmentChain() (common.Address, *scc.StateCommitmentChain) {
panic("SCC not configured on legacy networks - this is a programmer error")
}
func (b *BedrockAddresses) OptimismPortal() (common.Address, *bindings.OptimismPortal) {
return b.portalAddr, b.portal
}
...@@ -5,93 +5,85 @@ import ( ...@@ -5,93 +5,85 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services"
"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/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"
) )
// DepositsMap is a collection of deposit objects keyed
// on block hashes.
type DepositsMap map[common.Hash][]db.Deposit type DepositsMap map[common.Hash][]db.Deposit
type WithdrawalsMap map[common.Hash][]db.Withdrawal // Finalizations
// WithdrawalsMap is a collection of withdrawal objects keyed
// on block hashes.
type InitiatedWithdrawalMap map[common.Hash][]db.Withdrawal
// FinalizedWithdrawalsMap is a collection of finalized withdrawal
// objected keyed on block hashes.
type FinalizedWithdrawalsMap map[common.Hash][]db.FinalizedWithdrawal
type Bridge interface { type Bridge interface {
Address() common.Address Address() common.Address
GetDepositsByBlockRange(uint64, uint64) (DepositsMap, error) GetDepositsByBlockRange(context.Context, uint64, uint64) (DepositsMap, error)
GetWithdrawalsByBlockRange(uint64, uint64) (WithdrawalsMap, error)
String() string String() string
} }
type implConfig struct { type implConfig struct {
name string name string
impl string impl string
addr string addr common.Address
}
var defaultBridgeCfgs = map[uint64][]*implConfig{
// Devnet
900: {
{"Standard", "StandardBridge", predeploys.DevL1StandardBridge},
{"ETH", "ETHBridge", predeploys.DevL1StandardBridge},
},
// Goerli
5: {
{"Standard", "StandardBridge", "0xFf94B6C486350aD92561Ba09bad3a59df764Da92"},
{"ETH", "ETHBridge", "0xFf94B6C486350aD92561Ba09bad3a59df764Da92"},
},
} }
var customBridgeCfgs = map[uint64][]*implConfig{ var customBridgeCfgs = map[uint64][]*implConfig{
// Mainnet // Mainnet
1: { 1: {
{"BitBTC", "StandardBridge", "0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128"}, {"BitBTC", "StandardBridge", common.HexToAddress("0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128")},
{"DAI", "StandardBridge", "0x10E6593CDda8c58a1d0f14C5164B376352a55f2F"}, {"DAI", "StandardBridge", common.HexToAddress("0x10E6593CDda8c58a1d0f14C5164B376352a55f2F")},
{"wstETH", "StandardBridge", common.HexToAddress("0x76943C0D61395d8F2edF9060e1533529cAe05dE6")},
}, },
// Kovan // Kovan
42: { 42: {
{"BitBTC", "StandardBridge", "0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746"}, {"BitBTC", "StandardBridge", common.HexToAddress("0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746")},
{"USX", "StandardBridge", "0x40E862341b2416345F02c41Ac70df08525150dC7"}, {"USX", "StandardBridge", common.HexToAddress("0x40E862341b2416345F02c41Ac70df08525150dC7")},
{"DAI", "StandardBridge", "0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3"}, {"DAI", "StandardBridge", common.HexToAddress("0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3")},
{"wstETH", "StandardBridge", common.HexToAddress("0x65321bf24210b81500230dCEce14Faa70a9f50a7")},
}, },
} }
func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, ctx context.Context) (map[string]Bridge, error) { func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, addrs services.AddressManager) (map[string]Bridge, error) {
allCfgs := make([]*implConfig, 0) l1SBAddr, _ := addrs.L1StandardBridge()
allCfgs = append(allCfgs, defaultBridgeCfgs[chainID.Uint64()]...) allCfgs := []*implConfig{
{"Standard", "StandardBridge", l1SBAddr},
{"ETH", "ETHBridge", l1SBAddr},
}
allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...) allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...)
bridges := make(map[string]Bridge) bridges := make(map[string]Bridge)
for _, bridge := range allCfgs { for _, bridge := range allCfgs {
switch bridge.impl { switch bridge.impl {
case "StandardBridge": case "StandardBridge":
l1StandardBridgeAddress := common.HexToAddress(bridge.addr) l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
l1StandardBridgeFilter, err := bindings.NewL1StandardBridgeFilterer(l1StandardBridgeAddress, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
standardBridge := &StandardBridge{ standardBridge := &StandardBridge{
name: bridge.name, name: bridge.name,
ctx: ctx, address: bridge.addr,
address: l1StandardBridgeAddress, contract: l1SB,
client: client,
filterer: l1StandardBridgeFilter,
} }
bridges[bridge.name] = standardBridge bridges[bridge.name] = standardBridge
case "ETHBridge": case "ETHBridge":
l1StandardBridgeAddress := common.HexToAddress(bridge.addr) l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
l1EthBridgeFilter, err := bindings.NewL1StandardBridgeFilterer(l1StandardBridgeAddress, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ethBridge := &EthBridge{ ethBridge := &EthBridge{
name: bridge.name, name: bridge.name,
ctx: ctx, address: bridge.addr,
address: l1StandardBridgeAddress, contract: l1SB,
client: client,
filterer: l1EthBridgeFilter,
} }
bridges[bridge.name] = ethBridge bridges[bridge.name] = ethBridge
default: default:
...@@ -100,3 +92,12 @@ func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, ctx context ...@@ -100,3 +92,12 @@ func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, ctx context
} }
return bridges, nil return bridges, nil
} }
func StateCommitmentChainScanner(client bind.ContractFilterer, addrs services.AddressManager) (*scc.StateCommitmentChainFilterer, error) {
sccAddr, _ := addrs.StateCommitmentChain()
filter, err := scc.NewStateCommitmentChainFilterer(sccAddr, client)
if err != nil {
return nil, err
}
return filter, nil
}
package bridge
import (
"context"
"errors"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
// DepositsMap is a collection of deposit objects keyed
// on block hashes.
type DepositsMap map[common.Hash][]db.Deposit
<<<<<<< HEAD
// WithdrawalsMap is a collection of withdrawal objects keyed
// on block hashes.
type WithdrawalsMap map[common.Hash][]db.Withdrawal
// FinalizedWithdrawalsMap is a collection of finalized withdrawal
// objected keyed on block hashes.
=======
type InitiatedWithdrawalMap map[common.Hash][]db.Withdrawal
>>>>>>> 22c039efc (indexer: Upgrade L1 services)
type FinalizedWithdrawalsMap map[common.Hash][]db.FinalizedWithdrawal
type Bridge interface {
Address() common.Address
GetDepositsByBlockRange(context.Context, uint64, uint64) (DepositsMap, error)
String() string
}
type implConfig struct {
name string
impl string
addr common.Address
}
var customBridgeCfgs = map[uint64][]*implConfig{
// Mainnet
1: {
{"BitBTC", "StandardBridge", common.HexToAddress("0xaBA2c5F108F7E820C049D5Af70B16ac266c8f128")},
{"DAI", "StandardBridge", common.HexToAddress("0x10E6593CDda8c58a1d0f14C5164B376352a55f2F")},
{"wstETH", "StandardBridge", common.HexToAddress("0x76943C0D61395d8F2edF9060e1533529cAe05dE6")},
},
// Kovan
42: {
{"BitBTC", "StandardBridge", common.HexToAddress("0x0b651A42F32069d62d5ECf4f2a7e5Bd3E9438746")},
{"USX", "StandardBridge", common.HexToAddress("0x40E862341b2416345F02c41Ac70df08525150dC7")},
{"DAI", "StandardBridge", common.HexToAddress("0xb415e822C4983ecD6B1c1596e8a5f976cf6CD9e3")},
{"wstETH", "StandardBridge", common.HexToAddress("0x65321bf24210b81500230dCEce14Faa70a9f50a7")},
},
}
func BridgesByChainID(chainID *big.Int, client bind.ContractBackend, addrs services.AddressManager) (map[string]Bridge, error) {
l1SBAddr, _ := addrs.L1StandardBridge()
allCfgs := []*implConfig{
{"Standard", "StandardBridge", l1SBAddr},
{"ETH", "ETHBridge", l1SBAddr},
}
allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...)
bridges := make(map[string]Bridge)
for _, bridge := range allCfgs {
switch bridge.impl {
case "StandardBridge":
l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
if err != nil {
return nil, err
}
standardBridge := &StandardBridge{
name: bridge.name,
address: bridge.addr,
contract: l1SB,
}
bridges[bridge.name] = standardBridge
case "ETHBridge":
l1SB, err := bindings.NewL1StandardBridge(bridge.addr, client)
if err != nil {
return nil, err
}
ethBridge := &EthBridge{
name: bridge.name,
address: bridge.addr,
contract: l1SB,
}
bridges[bridge.name] = ethBridge
default:
return nil, errors.New("unsupported bridge")
}
}
return bridges, nil
}
func StateCommitmentChainScanner(client bind.ContractFilterer, addrs services.AddressManager) (*scc.StateCommitmentChainFilterer, error) {
sccAddr, _ := addrs.StateCommitmentChain()
filter, err := scc.NewStateCommitmentChainFilterer(sccAddr, client)
if err != nil {
return nil, err
}
return filter, nil
}
...@@ -5,33 +5,40 @@ import ( ...@@ -5,33 +5,40 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"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"
) )
type EthBridge struct { type EthBridge struct {
name string name string
ctx context.Context
address common.Address address common.Address
client bind.ContractFilterer contract *bindings.L1StandardBridge
filterer *bindings.L1StandardBridgeFilterer
} }
func (e *EthBridge) Address() common.Address { func (e *EthBridge) Address() common.Address {
return e.address return e.address
} }
func (e *EthBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, error) { func (e *EthBridge) GetDepositsByBlockRange(ctx context.Context, start, end uint64) (DepositsMap, error) {
depositsByBlockhash := make(DepositsMap) depositsByBlockhash := make(DepositsMap)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
iter, err := FilterETHDepositInitiatedWithRetry(e.ctx, e.filterer, &bind.FilterOpts{ var iter *bindings.L1StandardBridgeETHDepositInitiatedIterator
Start: start, err := backoff.Do(3, backoff.Exponential(), func() error {
End: &end, var err error
iter, err = e.contract.FilterETHDepositInitiated(opts, nil, nil)
return err
}) })
if err != nil { if err != nil {
logger.Error("Error fetching filter", "err", err) return nil, err
} }
defer iter.Close()
for iter.Next() { for iter.Next() {
depositsByBlockhash[iter.Event.Raw.BlockHash] = append( depositsByBlockhash[iter.Event.Raw.BlockHash] = append(
depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{ depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{
...@@ -43,41 +50,8 @@ func (e *EthBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, err ...@@ -43,41 +50,8 @@ func (e *EthBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, err
LogIndex: iter.Event.Raw.Index, LogIndex: iter.Event.Raw.Index,
}) })
} }
if err := iter.Error(); err != nil {
return nil, err
}
return depositsByBlockhash, nil
}
func (s *EthBridge) GetWithdrawalsByBlockRange(start, end uint64) (WithdrawalsMap, error) {
withdrawalsByBlockHash := make(WithdrawalsMap)
iter, err := FilterETHWithdrawalFinalizedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{
Start: start,
End: &end,
})
if err != nil {
logger.Error("Error fetching filter", "err", err)
}
for iter.Next() {
withdrawalsByBlockHash[iter.Event.Raw.BlockHash] = append(
withdrawalsByBlockHash[iter.Event.Raw.BlockHash], db.Withdrawal{
TxHash: iter.Event.Raw.TxHash,
FromAddress: iter.Event.From,
ToAddress: iter.Event.To,
Amount: iter.Event.Amount,
Data: iter.Event.ExtraData,
LogIndex: iter.Event.Raw.Index,
})
}
if err := iter.Error(); err != nil {
return nil, err
}
return withdrawalsByBlockHash, nil return depositsByBlockhash, iter.Error()
} }
func (e *EthBridge) String() string { func (e *EthBridge) String() string {
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
) )
...@@ -12,61 +12,13 @@ import ( ...@@ -12,61 +12,13 @@ import (
// calls. // calls.
var clientRetryInterval = 5 * time.Second var clientRetryInterval = 5 * time.Second
// FilterETHDepositInitiatedWithRetry retries the given func until it succeeds, // FilterStateBatchAppendedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call. // waiting for clientRetryInterval duration after every call.
func FilterETHDepositInitiatedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeETHDepositInitiatedIterator, error) { func FilterStateBatchAppendedWithRetry(ctx context.Context, filterer *scc.StateCommitmentChainFilterer, opts *bind.FilterOpts) (*scc.StateCommitmentChainStateBatchAppendedIterator, error) {
for { for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout) ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt opts.Context = ctxt
res, err := filterer.FilterETHDepositInitiated(opts, nil, nil) res, err := filterer.FilterStateBatchAppended(opts, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterERC20DepositInitiatedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterERC20DepositInitiatedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeERC20DepositInitiatedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterERC20DepositInitiated(opts, nil, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterETHWithdrawalFinalizedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterETHWithdrawalFinalizedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeETHWithdrawalFinalizedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterETHWithdrawalFinalized(opts, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterERC20WithdrawalFinalizedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterERC20WithdrawalFinalizedWithRetry(ctx context.Context, filterer *bindings.L1StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L1StandardBridgeERC20WithdrawalFinalizedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterERC20WithdrawalFinalized(opts, nil, nil, nil)
cancel() cancel()
if err == nil { if err == nil {
return res, nil return res, nil
......
package bridge
import (
"context"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type Portal struct {
address common.Address
contract *bindings.OptimismPortal
}
func NewPortal(addrs services.AddressManager) *Portal {
address, contract := addrs.OptimismPortal()
return &Portal{
address: address,
contract: contract,
}
}
func (p *Portal) Address() common.Address {
return p.address
}
func (p *Portal) GetFinalizedWithdrawalsByBlockRange(ctx context.Context, start, end uint64) (FinalizedWithdrawalsMap, error) {
wdsByBlockHash := make(FinalizedWithdrawalsMap)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
var iter *bindings.OptimismPortalWithdrawalFinalizedIterator
err := backoff.Do(3, backoff.Exponential(), func() error {
var err error
iter, err = p.contract.FilterWithdrawalFinalized(opts, nil)
return err
})
if err != nil {
return nil, err
}
defer iter.Close()
for iter.Next() {
wdsByBlockHash[iter.Event.Raw.BlockHash] = append(
wdsByBlockHash[iter.Event.Raw.BlockHash], db.FinalizedWithdrawal{
TxHash: iter.Event.Raw.TxHash,
WithdrawalHash: iter.Event.WithdrawalHash,
Success: iter.Event.Success,
LogIndex: iter.Event.Raw.Index,
},
)
}
return wdsByBlockHash, iter.Error()
}
...@@ -5,33 +5,40 @@ import ( ...@@ -5,33 +5,40 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"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"
) )
type StandardBridge struct { type StandardBridge struct {
name string name string
ctx context.Context
address common.Address address common.Address
client bind.ContractFilterer contract *bindings.L1StandardBridge
filterer *bindings.L1StandardBridgeFilterer
} }
func (s *StandardBridge) Address() common.Address { func (s *StandardBridge) Address() common.Address {
return s.address return s.address
} }
func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, error) { func (s *StandardBridge) GetDepositsByBlockRange(ctx context.Context, start, end uint64) (DepositsMap, error) {
depositsByBlockhash := make(DepositsMap) depositsByBlockhash := make(DepositsMap)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
iter, err := FilterERC20DepositInitiatedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{ var iter *bindings.L1StandardBridgeERC20DepositInitiatedIterator
Start: start, err := backoff.Do(3, backoff.Exponential(), func() error {
End: &end, var err error
iter, err = s.contract.FilterERC20DepositInitiated(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
logger.Error("Error fetching filter", "err", err) return nil, err
} }
defer iter.Close()
for iter.Next() { for iter.Next() {
depositsByBlockhash[iter.Event.Raw.BlockHash] = append( depositsByBlockhash[iter.Event.Raw.BlockHash] = append(
depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{ depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{
...@@ -45,43 +52,8 @@ func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap ...@@ -45,43 +52,8 @@ func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap
LogIndex: iter.Event.Raw.Index, LogIndex: iter.Event.Raw.Index,
}) })
} }
if err := iter.Error(); err != nil {
return nil, err
}
return depositsByBlockhash, nil
}
func (s *StandardBridge) GetWithdrawalsByBlockRange(start, end uint64) (WithdrawalsMap, error) {
withdrawalsByBlockHash := make(WithdrawalsMap)
iter, err := FilterERC20WithdrawalFinalizedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{
Start: start,
End: &end,
})
if err != nil {
logger.Error("Error fetching filter", "err", err)
}
for iter.Next() {
withdrawalsByBlockHash[iter.Event.Raw.BlockHash] = append(
withdrawalsByBlockHash[iter.Event.Raw.BlockHash], db.Withdrawal{
TxHash: iter.Event.Raw.TxHash,
L1Token: iter.Event.L1Token,
L2Token: iter.Event.L2Token,
FromAddress: iter.Event.From,
ToAddress: iter.Event.To,
Amount: iter.Event.Amount,
Data: iter.Event.ExtraData,
LogIndex: iter.Event.Raw.Index,
})
}
if err := iter.Error(); err != nil {
return nil, err
}
return withdrawalsByBlockHash, nil return depositsByBlockhash, iter.Error()
} }
func (s *StandardBridge) String() string { func (s *StandardBridge) String() string {
......
...@@ -17,9 +17,8 @@ import ( ...@@ -17,9 +17,8 @@ import (
) )
const ( const (
DefaultConnectionTimeout = 30 * time.Second DefaultConnectionTimeout = 30 * time.Second
DefaultConfDepth uint64 = 20 DefaultMaxBatchSize = 100
DefaultMaxBatchSize = 100
) )
type NewHeader struct { type NewHeader struct {
......
package l1 package l1
import ( import (
"github.com/ethereum/go-ethereum/ethclient" "context"
"github.com/ethereum-optimism/optimism/indexer/bindings/legacy/scc"
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/indexer/services/l1/bridge"
"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"
) )
func QueryERC20(address common.Address, client *ethclient.Client) (*db.Token, error) { func QueryStateBatches(filterer *scc.StateCommitmentChainFilterer, startHeight, endHeight uint64, ctx context.Context) (map[common.Hash][]db.StateBatch, error) {
contract, err := bindings.NewERC20(address, client) batches := make(map[common.Hash][]db.StateBatch)
if err != nil {
return nil, err
}
name, err := contract.Name(&bind.CallOpts{})
if err != nil {
return nil, err
}
symbol, err := contract.Symbol(&bind.CallOpts{}) iter, err := bridge.FilterStateBatchAppendedWithRetry(ctx, filterer, &bind.FilterOpts{
Start: startHeight,
End: &endHeight,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
decimals, err := contract.Decimals(&bind.CallOpts{}) defer iter.Close()
if err != nil { for iter.Next() {
return nil, err batches[iter.Event.Raw.BlockHash] = append(
batches[iter.Event.Raw.BlockHash], db.StateBatch{
Index: iter.Event.BatchIndex,
Root: iter.Event.BatchRoot,
Size: iter.Event.BatchSize,
PrevTotal: iter.Event.PrevTotalElements,
ExtraData: iter.Event.ExtraData,
BlockHash: iter.Event.Raw.BlockHash,
})
} }
return batches, iter.Error()
return &db.Token{
Name: name,
Symbol: symbol,
Decimals: decimals,
}, nil
} }
This diff is collapsed.
...@@ -7,75 +7,76 @@ import ( ...@@ -7,75 +7,76 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"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/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type DepositsMap map[common.Hash][]db.Deposit // Finalizations
type WithdrawalsMap map[common.Hash][]db.Withdrawal type WithdrawalsMap map[common.Hash][]db.Withdrawal
type Bridge interface { type Bridge interface {
Address() common.Address Address() common.Address
GetDepositsByBlockRange(uint64, uint64) (DepositsMap, error) GetWithdrawalsByBlockRange(context.Context, uint64, uint64) (WithdrawalsMap, error)
GetWithdrawalsByBlockRange(uint64, uint64) (WithdrawalsMap, error)
String() string String() string
} }
type implConfig struct { type implConfig struct {
name string name string
impl string impl string
addr string addr common.Address
} }
var defaultBridgeCfgs = map[uint64][]*implConfig{ var defaultBridgeCfgs = []*implConfig{
// Devnet {"Standard", "StandardBridge", predeploys.L2StandardBridgeAddr},
901: {
{"Standard", "StandardBridge", L2StandardBridgeAddr},
},
// Goerli Alpha Testnet
28528: {
{"Standard", "StandardBridge", L2StandardBridgeAddr},
},
} }
var customBridgeCfgs = map[uint64][]*implConfig{ var customBridgeCfgs = map[uint64][]*implConfig{
// Mainnet // Mainnet
10: { 10: {
{"BitBTC", StandardBridgeImpl, "0x158F513096923fF2d3aab2BcF4478536de6725e2"}, {"BitBTC", StandardBridgeImpl, common.HexToAddress("0x158F513096923fF2d3aab2BcF4478536de6725e2")},
//{"DAI", "DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"}, //{"DAI", "DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"},
{"wstETH", StandardBridgeImpl, common.HexToAddress("0x8E01013243a96601a86eb3153F0d9Fa4fbFb6957")},
}, },
// Kovan // Kovan
69: { 69: {
{"BitBTC", StandardBridgeImpl, "0x0CFb46528a7002a7D8877a5F7a69b9AaF1A9058e"}, {"BitBTC", StandardBridgeImpl, common.HexToAddress("0x0CFb46528a7002a7D8877a5F7a69b9AaF1A9058e")},
{"USX", StandardBridgeImpl, "0xB4d37826b14Cd3CB7257A2A5094507d701fe715f"}, {"USX", StandardBridgeImpl, common.HexToAddress("0xB4d37826b14Cd3CB7257A2A5094507d701fe715f")},
{"wstETH", StandardBridgeImpl, common.HexToAddress("0x2E34e7d705AfaC3C4665b6feF31Aa394A1c81c92")},
//{"DAI", " DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"}, //{"DAI", " DAIBridge", "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"},
}, },
} }
func BridgesByChainID(chainID *big.Int, client bind.ContractFilterer, ctx context.Context) (map[string]Bridge, error) { func BridgesByChainID(chainID *big.Int, client *ethclient.Client, isBedrock bool) (map[string]Bridge, error) {
allCfgs := make([]*implConfig, 0) allCfgs := make([]*implConfig, 0)
allCfgs = append(allCfgs, defaultBridgeCfgs[chainID.Uint64()]...) allCfgs = append(allCfgs, defaultBridgeCfgs...)
allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...) allCfgs = append(allCfgs, customBridgeCfgs[chainID.Uint64()]...)
var l2L1MP *bindings.L2ToL1MessagePasser
var err error
if isBedrock {
l2L1MP, err = bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, client)
if err != nil {
return nil, err
}
}
bridges := make(map[string]Bridge) bridges := make(map[string]Bridge)
for _, bridge := range allCfgs { for _, bridge := range allCfgs {
switch bridge.impl { switch bridge.impl {
case "StandardBridge": case "StandardBridge":
l2StandardBridgeAddress := common.HexToAddress(bridge.addr) l2SB, err := bindings.NewL2StandardBridge(bridge.addr, client)
l2StandardBridgeFilter, err := bindings.NewL2StandardBridgeFilterer(l2StandardBridgeAddress, client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
bridges[bridge.name] = &StandardBridge{
standardBridge := &StandardBridge{ name: bridge.name,
name: bridge.name, address: bridge.addr,
ctx: ctx, client: client,
address: l2StandardBridgeAddress, l2SB: l2SB,
client: client, l2L1MP: l2L1MP,
filterer: l2StandardBridgeFilter, isBedrock: isBedrock,
} }
bridges[bridge.name] = standardBridge
default: default:
return nil, errors.New("unsupported bridge") return nil, errors.New("unsupported bridge")
} }
......
package bridge
import (
"context"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
)
// clientRetryInterval is the interval to wait between retrying client API
// calls.
var clientRetryInterval = 5 * time.Second
// FilterWithdrawalInitiatedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterWithdrawalInitiatedWithRetry(ctx context.Context, filterer *bindings.L2StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L2StandardBridgeWithdrawalInitiatedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterWithdrawalInitiated(opts, nil, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
// FilterDepositFinalizedWithRetry retries the given func until it succeeds,
// waiting for clientRetryInterval duration after every call.
func FilterDepositFinalizedWithRetry(ctx context.Context, filterer *bindings.L2StandardBridgeFilterer, opts *bind.FilterOpts) (*bindings.L2StandardBridgeDepositFinalizedIterator, error) {
for {
ctxt, cancel := context.WithTimeout(ctx, DefaultConnectionTimeout)
opts.Context = ctxt
res, err := filterer.FilterDepositFinalized(opts, nil, nil, nil)
cancel()
if err == nil {
return res, nil
}
logger.Error("Error fetching filter", "err", err)
time.Sleep(clientRetryInterval)
}
}
...@@ -5,83 +5,112 @@ import ( ...@@ -5,83 +5,112 @@ import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"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"
) )
type StandardBridge struct { type StandardBridge struct {
name string name string
ctx context.Context address common.Address
address common.Address client *ethclient.Client
client bind.ContractFilterer l2SB *bindings.L2StandardBridge
filterer *bindings.L2StandardBridgeFilterer l2L1MP *bindings.L2ToL1MessagePasser
isBedrock bool
} }
func (s *StandardBridge) Address() common.Address { func (s *StandardBridge) Address() common.Address {
return s.address return s.address
} }
func (s *StandardBridge) GetDepositsByBlockRange(start, end uint64) (DepositsMap, error) { func (s *StandardBridge) GetWithdrawalsByBlockRange(ctx context.Context, start, end uint64) (WithdrawalsMap, error) {
depositsByBlockhash := make(DepositsMap) withdrawalsByBlockhash := make(map[common.Hash][]db.Withdrawal)
opts := &bind.FilterOpts{
Context: ctx,
Start: start,
End: &end,
}
iter, err := FilterDepositFinalizedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{ var iter *bindings.L2StandardBridgeWithdrawalInitiatedIterator
Start: start, err := backoff.Do(3, backoff.Exponential(), func() error {
End: &end, var err error
iter, err = s.l2SB.FilterWithdrawalInitiated(opts, nil, nil, nil)
return err
}) })
if err != nil { if err != nil {
logger.Error("Error fetching filter", "err", err) return nil, err
} }
receipts := make(map[common.Hash]*types.Receipt)
defer iter.Close()
for iter.Next() { for iter.Next() {
depositsByBlockhash[iter.Event.Raw.BlockHash] = append( ev := iter.Event
depositsByBlockhash[iter.Event.Raw.BlockHash], db.Deposit{ if s.isBedrock {
TxHash: iter.Event.Raw.TxHash, receipt := receipts[ev.Raw.TxHash]
L1Token: iter.Event.L1Token, if receipt == nil {
L2Token: iter.Event.L2Token, receipt, err = s.client.TransactionReceipt(ctx, ev.Raw.TxHash)
FromAddress: iter.Event.From, if err != nil {
ToAddress: iter.Event.To, return nil, err
Amount: iter.Event.Amount, }
Data: iter.Event.ExtraData, receipts[ev.Raw.TxHash] = receipt
LogIndex: iter.Event.Raw.Index, }
})
}
if err := iter.Error(); err != nil {
return nil, err
}
return depositsByBlockhash, nil var withdrawalInitiated *bindings.L2ToL1MessagePasserMessagePassed
} for _, eLog := range receipt.Logs {
if len(eLog.Topics) == 0 || eLog.Topics[0] != withdrawals.MessagePassedTopic {
continue
}
func (s *StandardBridge) GetWithdrawalsByBlockRange(start, end uint64) (WithdrawalsMap, error) { if withdrawalInitiated != nil {
withdrawalsByBlockhash := make(map[common.Hash][]db.Withdrawal) logger.Warn("detected multiple withdrawal initiated events! ignoring", "tx_hash", ev.Raw.TxHash)
continue
}
iter, err := FilterWithdrawalInitiatedWithRetry(s.ctx, s.filterer, &bind.FilterOpts{ withdrawalInitiated, err = s.l2L1MP.ParseMessagePassed(*eLog)
Start: start, if err != nil {
End: &end, return nil, err
}) }
if err != nil { }
logger.Error("Error fetching filter", "err", err)
}
for iter.Next() { hash, err := withdrawals.WithdrawalHash(withdrawalInitiated)
withdrawalsByBlockhash[iter.Event.Raw.BlockHash] = append( if err != nil {
withdrawalsByBlockhash[iter.Event.Raw.BlockHash], db.Withdrawal{ return nil, err
TxHash: iter.Event.Raw.TxHash, }
L1Token: iter.Event.L1Token,
L2Token: iter.Event.L2Token, withdrawalsByBlockhash[ev.Raw.BlockHash] = append(
FromAddress: iter.Event.From, withdrawalsByBlockhash[ev.Raw.BlockHash], db.Withdrawal{
ToAddress: iter.Event.To, TxHash: ev.Raw.TxHash,
Amount: iter.Event.Amount, L1Token: ev.L1Token,
Data: iter.Event.ExtraData, L2Token: ev.L2Token,
LogIndex: iter.Event.Raw.Index, FromAddress: ev.From,
}) ToAddress: ev.To,
} Amount: ev.Amount,
if err := iter.Error(); err != nil { Data: ev.ExtraData,
return nil, err LogIndex: ev.Raw.Index,
BedrockHash: &hash,
},
)
} else {
withdrawalsByBlockhash[ev.Raw.BlockHash] = append(
withdrawalsByBlockhash[ev.Raw.BlockHash], db.Withdrawal{
TxHash: ev.Raw.TxHash,
L1Token: ev.L1Token,
L2Token: ev.L2Token,
FromAddress: ev.From,
ToAddress: ev.To,
Amount: ev.Amount,
Data: ev.ExtraData,
LogIndex: ev.Raw.Index,
},
)
}
} }
return withdrawalsByBlockhash, nil return withdrawalsByBlockhash, iter.Error()
} }
func (s *StandardBridge) String() string { func (s *StandardBridge) String() string {
......
...@@ -147,8 +147,7 @@ func (f *ConfirmedHeaderSelector) NewHead( ...@@ -147,8 +147,7 @@ func (f *ConfirmedHeaderSelector) NewHead(
return headers, nil return headers, nil
} }
func NewConfirmedHeaderSelector(cfg HeaderSelectorConfig) (*ConfirmedHeaderSelector, func NewConfirmedHeaderSelector(cfg HeaderSelectorConfig) (*ConfirmedHeaderSelector, error) {
error) {
if cfg.ConfDepth == 0 { if cfg.ConfDepth == 0 {
return nil, errors.New("ConfDepth must be greater than zero") return nil, errors.New("ConfDepth must be greater than zero")
} }
......
This diff is collapsed.
package l2 package query
import ( import (
"github.com/ethereum-optimism/optimism/indexer/db" "github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"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/ethclient" "github.com/ethereum/go-ethereum/ethclient"
) )
func QueryERC20(address common.Address, client *ethclient.Client) (*db.Token, error) { func NewERC20(address common.Address, client *ethclient.Client) (*db.Token, error) {
contract, err := bindings.NewERC20(address, client) contract, err := bindings.NewERC20(address, client)
if err != nil { if err != nil {
return nil, err return nil, err
......
package query
import (
"context"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
// HeaderByNumberWithRetry retries getting headers.
func HeaderByNumberWithRetry(ctx context.Context, client *ethclient.Client) (*types.Header, error) {
var res *types.Header
err := backoff.DoCtx(ctx, 3, backoff.Exponential(), func() error {
var err error
res, err = client.HeaderByNumber(ctx, nil)
return err
})
return res, err
}
...@@ -113,12 +113,11 @@ describe('ERC721 Bridge', () => { ...@@ -113,12 +113,11 @@ describe('ERC721 Bridge', () => {
) )
// Create a L2 Standard ERC721 with the Standard ERC721 Factory // Create a L2 Standard ERC721 with the Standard ERC721 Factory
const tx = const tx = await OptimismMintableERC721Factory.createOptimismMintableERC721(
await OptimismMintableERC721Factory.createStandardOptimismMintableERC721( L1ERC721.address,
L1ERC721.address, 'L2ERC721',
'L2ERC721', 'L2'
'L2' )
)
const receipt = await tx.wait() const receipt = await tx.wait()
// Get the OptimismMintableERC721Created event // Get the OptimismMintableERC721Created event
......
...@@ -188,6 +188,7 @@ var ( ...@@ -188,6 +188,7 @@ var (
utils.IPCPathFlag, utils.IPCPathFlag,
utils.InsecureUnlockAllowedFlag, utils.InsecureUnlockAllowedFlag,
utils.RPCGlobalGasCap, utils.RPCGlobalGasCap,
utils.RPCGlobalEVMTimeoutFlag,
} }
whisperFlags = []cli.Flag{ whisperFlags = []cli.Flag{
......
...@@ -178,6 +178,7 @@ var AppHelpFlagGroups = []flagGroup{ ...@@ -178,6 +178,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.RPCPortFlag, utils.RPCPortFlag,
utils.RPCApiFlag, utils.RPCApiFlag,
utils.RPCGlobalGasCap, utils.RPCGlobalGasCap,
utils.RPCGlobalEVMTimeoutFlag,
utils.RPCCORSDomainFlag, utils.RPCCORSDomainFlag,
utils.RPCVirtualHostsFlag, utils.RPCVirtualHostsFlag,
utils.WSEnabledFlag, utils.WSEnabledFlag,
......
...@@ -518,6 +518,11 @@ var ( ...@@ -518,6 +518,11 @@ var (
Name: "rpc.gascap", Name: "rpc.gascap",
Usage: "Sets a cap on gas that can be used in eth_call/estimateGas", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas",
} }
RPCGlobalEVMTimeoutFlag = &cli.DurationFlag{
Name: "rpc.evmtimeout",
Usage: "Sets a timeout used for eth_call (0=infinite)",
Value: eth.DefaultConfig.RPCEVMTimeout,
}
// Logging and debug settings // Logging and debug settings
EthStatsURLFlag = cli.StringFlag{ EthStatsURLFlag = cli.StringFlag{
Name: "ethstats", Name: "ethstats",
...@@ -1660,6 +1665,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { ...@@ -1660,6 +1665,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { if ctx.GlobalIsSet(RPCGlobalGasCap.Name) {
cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name)) cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name))
} }
if ctx.GlobalIsSet(RPCGlobalEVMTimeoutFlag.Name) {
cfg.RPCEVMTimeout = ctx.Duration(RPCGlobalEVMTimeoutFlag.Name)
}
// Override any default configs for hard coded networks. // Override any default configs for hard coded networks.
switch { switch {
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"time"
"github.com/ethereum-optimism/optimism/l2geth/accounts" "github.com/ethereum-optimism/optimism/l2geth/accounts"
"github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common"
...@@ -400,6 +401,10 @@ func (b *EthAPIBackend) RPCGasCap() *big.Int { ...@@ -400,6 +401,10 @@ func (b *EthAPIBackend) RPCGasCap() *big.Int {
return b.eth.config.RPCGasCap return b.eth.config.RPCGasCap
} }
func (b *EthAPIBackend) RPCEVMTimeout() time.Duration {
return b.eth.config.RPCEVMTimeout
}
func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
sections, _, _ := b.eth.bloomIndexer.Sections() sections, _, _ := b.eth.bloomIndexer.Sections()
return params.BloomBitsBlocks, sections return params.BloomBitsBlocks, sections
......
...@@ -57,7 +57,8 @@ var DefaultConfig = Config{ ...@@ -57,7 +57,8 @@ var DefaultConfig = Config{
GasPrice: big.NewInt(params.GWei), GasPrice: big.NewInt(params.GWei),
Recommit: 3 * time.Second, Recommit: 3 * time.Second,
}, },
TxPool: core.DefaultTxPoolConfig, TxPool: core.DefaultTxPoolConfig,
RPCEVMTimeout: 5 * time.Second,
GPO: gasprice.Config{ GPO: gasprice.Config{
Blocks: 20, Blocks: 20,
Percentile: 60, Percentile: 60,
...@@ -169,6 +170,9 @@ type Config struct { ...@@ -169,6 +170,9 @@ type Config struct {
// RPCGasCap is the global gas cap for eth-call variants. // RPCGasCap is the global gas cap for eth-call variants.
RPCGasCap *big.Int `toml:",omitempty"` RPCGasCap *big.Int `toml:",omitempty"`
// RPCEVMTimeout is the global timeout for eth-call. (0=infinite)
RPCEVMTimeout time.Duration
// Checkpoint is a hardcoded checkpoint which can be nil. // Checkpoint is a hardcoded checkpoint which can be nil.
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
......
...@@ -20,7 +20,6 @@ package graphql ...@@ -20,7 +20,6 @@ package graphql
import ( import (
"context" "context"
"errors" "errors"
"time"
ethereum "github.com/ethereum-optimism/optimism/l2geth" ethereum "github.com/ethereum-optimism/optimism/l2geth"
"github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common"
...@@ -776,7 +775,7 @@ func (b *Block) Call(ctx context.Context, args struct { ...@@ -776,7 +775,7 @@ func (b *Block) Call(ctx context.Context, args struct {
return nil, err return nil, err
} }
} }
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, &vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, &vm.Config{}, b.backend.RPCEVMTimeout(), b.backend.RPCGasCap())
status := hexutil.Uint64(1) status := hexutil.Uint64(1)
if failed { if failed {
status = 0 status = 0
...@@ -842,7 +841,7 @@ func (p *Pending) Call(ctx context.Context, args struct { ...@@ -842,7 +841,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.CallArgs Data ethapi.CallArgs
}) (*CallResult, error) { }) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, &vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, &vm.Config{}, p.backend.RPCEVMTimeout(), p.backend.RPCGasCap())
status := hexutil.Uint64(1) status := hexutil.Uint64(1)
if failed { if failed {
status = 0 status = 0
......
...@@ -963,7 +963,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr ...@@ -963,7 +963,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
if overrides != nil { if overrides != nil {
accounts = *overrides accounts = *overrides
} }
result, _, failed, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, &vm.Config{}, 5*time.Second, s.b.RPCGasCap()) result, _, failed, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, &vm.Config{}, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -20,6 +20,7 @@ package ethapi ...@@ -20,6 +20,7 @@ package ethapi
import ( import (
"context" "context"
"math/big" "math/big"
"time"
"github.com/ethereum-optimism/optimism/l2geth/accounts" "github.com/ethereum-optimism/optimism/l2geth/accounts"
"github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common"
...@@ -45,7 +46,8 @@ type Backend interface { ...@@ -45,7 +46,8 @@ type Backend interface {
ChainDb() ethdb.Database ChainDb() ethdb.Database
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
ExtRPCEnabled() bool ExtRPCEnabled() bool
RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout (0=infinite) for eth_call over rpc: DoS protection
// Blockchain API // Blockchain API
SetHead(number uint64) SetHead(number uint64)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/solc" "github.com/ethereum-optimism/optimism/op-bindings/solc"
) )
const DeployerWhitelistStorageLayoutJSON = "{\"storage\":[{\"astId\":2853,\"contract\":\"contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist\",\"label\":\"owner\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_address\"},{\"astId\":2858,\"contract\":\"contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist\",\"label\":\"whitelist\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_mapping(t_address,t_bool)\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_mapping(t_address,t_bool)\":{\"encoding\":\"mapping\",\"label\":\"mapping(address =\u003e bool)\",\"numberOfBytes\":\"32\",\"key\":\"t_address\",\"value\":\"t_bool\"}}}" const DeployerWhitelistStorageLayoutJSON = "{\"storage\":[{\"astId\":2850,\"contract\":\"contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist\",\"label\":\"owner\",\"offset\":0,\"slot\":\"0\",\"type\":\"t_address\"},{\"astId\":2855,\"contract\":\"contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist\",\"label\":\"whitelist\",\"offset\":0,\"slot\":\"1\",\"type\":\"t_mapping(t_address,t_bool)\"}],\"types\":{\"t_address\":{\"encoding\":\"inplace\",\"label\":\"address\",\"numberOfBytes\":\"20\"},\"t_bool\":{\"encoding\":\"inplace\",\"label\":\"bool\",\"numberOfBytes\":\"1\"},\"t_mapping(t_address,t_bool)\":{\"encoding\":\"mapping\",\"label\":\"mapping(address =\u003e bool)\",\"numberOfBytes\":\"32\",\"key\":\"t_address\",\"value\":\"t_bool\"}}}"
var DeployerWhitelistStorageLayout = new(solc.StorageLayout) var DeployerWhitelistStorageLayout = new(solc.StorageLayout)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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