Commit 9e212800 authored by Ethen Pociask's avatar Ethen Pociask

Merge branch 'develop' of https://github.com/epociask/optimism into indexer.runbook

parents cfa627b4 587999a7
This diff is collapsed.
##################################################
# Getting Started #
##################################################
# Admin account
export GS_ADMIN_ADDRESS=
export GS_ADMIN_PRIVATE_KEY=
# Batcher account
export GS_BATCHER_ADDRESS=
export GS_BATCHER_PRIVATE_KEY=
# Proposer account
export GS_PROPOSER_ADDRESS=
export GS_PROPOSER_PRIVATE_KEY=
# Sequencer account
export GS_SEQUENCER_ADDRESS=
export GS_SEQUENCER_PRIVATE_KEY=
##################################################
# op-node Configuration #
##################################################
# The kind of RPC provider, used to inform optimal transactions receipts
# fetching. Valid options: alchemy, quicknode, infura, parity, nethermind,
# debug_geth, erigon, basic, any.
export L1_RPC_KIND=
##################################################
# Contract Deployment #
##################################################
# RPC URL for the L1 network to interact with
export L1_RPC_URL=
# Salt used via CREATE2 to determine implementation addresses
# NOTE: If you want to deploy contracts from scratch you MUST reload this
# variable to ensure the salt is regenerated and the contracts are
# deployed to new addresses (otherwise deployment will fail)
export IMPL_SALT=$(openssl rand -hex 32)
# Name for the deployed network
export DEPLOYMENT_CONTEXT=getting-started
# Optional Tenderly details for simulation link during deployment
export TENDERLY_PROJECT=
export TENDERLY_USERNAME=
# Optional Etherscan API key for contract verification
export ETHERSCAN_API_KEY=
# Private key to use for contract deployments, you don't need to worry about
# this for the Getting Started guide.
export PRIVATE_KEY=
d85718785859dc0b5a095d2302d1a20ec06ab77a
1a2e2e071ef0a1b6f41fdcba773b04c30498752f
......@@ -15,11 +15,12 @@ jobs:
runs-on: ubuntu-latest
# map the step outputs to job outputs
outputs:
fault-mon: ${{ steps.packages.outputs.fault-mon }}
balance-mon: ${{ steps.packages.outputs.balance-mon }}
drippie-mon: ${{ steps.packages.outputs.drippie-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }}
fault-mon: ${{ steps.packages.outputs.fault-mon }}
multisig-mon: ${{ steps.packages.outputs.multisig-mon }}
replica-mon: ${{ steps.packages.outputs.replica-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }}
canary-docker-tag: ${{ steps.docker-image-name.outputs.canary-docker-tag }}
op-exporter: ${{ steps.packages.outputs.op-exporter }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
......@@ -97,6 +98,33 @@ jobs:
push: true
tags: ethereumoptimism/balance-mon:${{ needs.canary-publish.outputs.canary-docker-tag }}
multisig-mon:
name: Publish Multisig Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
if: needs.canary-publish.outputs.multisig-mon != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: multisig-mon
push: true
tags: ethereumoptimism/multisig-mon:${{ needs.canary-publish.outputs.canary-docker-tag }}
drippie-mon:
name: Publish Drippie Monitor Version ${{ needs.canary-publish.outputs.canary-docker-tag }}
needs: canary-publish
......
......@@ -16,11 +16,12 @@ jobs:
if: github.repository == 'ethereum-optimism/optimism'
# map the step outputs to job outputs
outputs:
fault-mon: ${{ steps.packages.outputs.fault-mon }}
balance-mon: ${{ steps.packages.outputs.drippie-mon }}
balance-mon: ${{ steps.packages.outputs.balance-mon }}
drippie-mon: ${{ steps.packages.outputs.drippie-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }}
fault-mon: ${{ steps.packages.outputs.fault-mon }}
multisig-mon: ${{ steps.packages.outputs.multisig-mon }}
replica-mon: ${{ steps.packages.outputs.replica-mon }}
wd-mon: ${{ steps.packages.outputs.wd-mon }}
op-exporter: ${{ steps.packages.outputs.op-exporter }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
# Permissions necessary for Changesets to push a new branch and open PRs
......@@ -186,6 +187,33 @@ jobs:
push: true
tags: ethereumoptimism/balance-mon:${{ needs.release.outputs.balance-mon }},ethereumoptimism/balance-mon:latest
multisig-mon:
name: Publish Multisig Monitor Version ${{ needs.release.outputs.multisig-mon }}
needs: release
if: needs.release.outputs.multisig-mon != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.packages
target: multisig-mon
push: true
tags: ethereumoptimism/multisig-mon:${{ needs.release.outputs.multisig-mon }},ethereumoptimism/multisig-mon:latest
drippie-mon:
name: Publish Drippie Monitor Version ${{ needs.release.outputs.drippie-mon }}
needs: release
......
......@@ -30,6 +30,7 @@ packages/contracts-bedrock/deployments/anvil
.env
.env*
!.env.example
!.envrc.example
*.log
.devnet
......
module.exports = {
$schema: 'http://json.schemastore.org/prettierrc',
plugins: ['prettier-plugin-solidity'],
plugins: [],
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
arrowParens: 'always',
overrides: [
{
files: '*.sol',
options: {
// These options are native to Prettier.
printWidth: 100,
tabWidth: 4,
useTabs: false,
singleQuote: false,
bracketSpacing: true,
// These options are specific to the Solidity Plugin
explicitTypes: 'always',
compiler: '>=0.8.15',
},
},
],
overrides: [],
}
......@@ -40,11 +40,7 @@ golang-docker:
.PHONY: golang-docker
submodules:
# CI will checkout submodules on its own (and fails on these commands)
if [ -z "$$GITHUB_ENV" ]; then \
git submodule init; \
git submodule update --recursive; \
fi
git submodule update --init --recursive
.PHONY: submodules
op-bindings:
......
......@@ -7,8 +7,8 @@ toolchain go1.21.1
require github.com/ethereum-optimism/optimism v0.0.0
require (
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)
replace github.com/ethereum-optimism/optimism v0.0.0 => ../../..
......@@ -4,9 +4,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
......@@ -2,10 +2,8 @@ package mipsevm
import (
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -22,27 +20,6 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/srcmap"
)
var (
StepBytes4 []byte
LoadKeccak256PreimagePartBytes4 []byte
LoadLocalDataBytes4 []byte
)
func init() {
mipsAbi, err := bindings.MIPSMetaData.GetAbi()
if err != nil {
panic(fmt.Errorf("failed to load MIPS ABI: %w", err))
}
StepBytes4 = mipsAbi.Methods["step"].ID[:4]
preimageAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
panic(fmt.Errorf("failed to load pre-image oracle ABI: %w", err))
}
LoadKeccak256PreimagePartBytes4 = preimageAbi.Methods["loadKeccak256PreimagePart"].ID[:4]
LoadLocalDataBytes4 = preimageAbi.Methods["loadLocalData"].ID[:4]
}
// LoadContracts loads the Cannon contracts, from op-bindings package
func LoadContracts() (*Contracts, error) {
var mips, oracle Contract
......@@ -56,34 +33,6 @@ func LoadContracts() (*Contracts, error) {
}, nil
}
// LoadContractsFromFiles loads the Cannon contracts, from local filesystem
func LoadContractsFromFiles() (*Contracts, error) {
mips, err := LoadContract("MIPS")
if err != nil {
return nil, err
}
oracle, err := LoadContract("PreimageOracle")
if err != nil {
return nil, err
}
return &Contracts{
MIPS: mips,
Oracle: oracle,
}, nil
}
func LoadContract(name string) (*Contract, error) {
dat, err := os.ReadFile(fmt.Sprintf("../../packages/contracts-bedrock/forge-artifacts/%s.sol/%s.json", name, name))
if err != nil {
return nil, fmt.Errorf("failed to read contract JSON definition of %q: %w", name, err)
}
var out Contract
if err := json.Unmarshal(dat, &out); err != nil {
return nil, fmt.Errorf("failed to parse contract JSON definition of %q: %w", name, err)
}
return &out, nil
}
type Contract struct {
DeployedBytecode struct {
Object hexutil.Bytes `json:"object"`
......
......@@ -3,6 +3,8 @@ package mipsevm
import (
"bytes"
"debug/elf"
"errors"
"fmt"
"io"
"math/big"
"os"
......@@ -11,6 +13,8 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state"
......@@ -76,13 +80,13 @@ func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
if stepWitness.HasPreimage() {
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
poInput, err := stepWitness.EncodePreimageOracleInput(0)
poInput, err := encodePreimageOracleInput(t, stepWitness, 0)
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
input := stepWitness.EncodeStepInput(0)
input := encodeStepInput(t, stepWitness, 0)
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoError(t, err, "evm should not fail")
require.Len(t, ret, 32, "expecting 32-byte state hash")
......@@ -101,6 +105,53 @@ func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
return evmPost
}
func encodeStepInput(t *testing.T, wit *StepWitness, localContext LocalContext) []byte {
mipsAbi, err := bindings.MIPSMetaData.GetAbi()
require.NoError(t, err)
input, err := mipsAbi.Pack("step", wit.State, wit.MemProof, new(big.Int).SetUint64(uint64(localContext)))
require.NoError(t, err)
return input
}
func encodePreimageOracleInput(t *testing.T, wit *StepWitness, localContext LocalContext) ([]byte, error) {
if wit.PreimageKey == ([32]byte{}) {
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
}
preimageAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err, "failed to load pre-image oracle ABI")
switch preimage.KeyType(wit.PreimageKey[0]) {
case preimage.LocalKeyType:
if len(wit.PreimageValue) > 32+8 {
return nil, fmt.Errorf("local pre-image exceeds maximum size of 32 bytes with key 0x%x", wit.PreimageKey)
}
preimagePart := wit.PreimageValue[8:]
var tmp [32]byte
copy(tmp[:], preimagePart)
input, err := preimageAbi.Pack("loadLocalData",
new(big.Int).SetBytes(wit.PreimageKey[1:]),
new(big.Int).SetUint64(uint64(localContext)),
tmp,
new(big.Int).SetUint64(uint64(len(preimagePart))),
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
)
require.NoError(t, err)
return input, nil
case preimage.Keccak256KeyType:
input, err := preimageAbi.Pack(
"loadKeccak256PreimagePart",
new(big.Int).SetUint64(uint64(wit.PreimageOffset)),
wit.PreimageValue[8:])
require.NoError(t, err)
return input, nil
default:
return nil, fmt.Errorf("unsupported pre-image type %d, cannot prepare preimage with key %x offset %d for oracle",
wit.PreimageKey[0], wit.PreimageKey, wit.PreimageOffset)
}
}
func TestEVM(t *testing.T) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err)
......@@ -241,7 +292,7 @@ func TestEVMFault(t *testing.T) {
State: initialState.EncodeWitness(),
MemProof: insnProof[:],
}
input := stepWitness.EncodeStepInput(0)
input := encodeStepInput(t, stepWitness, 0)
startingGas := uint64(30_000_000)
_, _, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, big.NewInt(0))
......
package mipsevm
import (
"encoding/binary"
"errors"
"fmt"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
type LocalContext uint64
type StepWitness struct {
......@@ -21,78 +13,6 @@ type StepWitness struct {
PreimageOffset uint32
}
func uint32ToBytes32(v uint32) []byte {
var out [32]byte
binary.BigEndian.PutUint32(out[32-4:], v)
return out[:]
}
func uint64ToBytes32(v uint64) []byte {
var out [32]byte
binary.BigEndian.PutUint64(out[32-8:], v)
return out[:]
}
func (wit *StepWitness) EncodeStepInput(localContext LocalContext) []byte {
abiStateLen := len(wit.State)
if abiStateLen%32 != 0 {
abiStateLen += 32 - (abiStateLen % 32)
}
// pad state to 32 byte multiple per ABI
abiState := make([]byte, abiStateLen)
copy(abiState, wit.State)
var input []byte
input = append(input, StepBytes4...)
input = append(input, uint32ToBytes32(32*3)...) // state data offset in bytes
input = append(input, uint32ToBytes32(32*3+32+uint32(len(abiState)))...) // proof data offset in bytes
input = append(input, uint64ToBytes32(uint64(localContext))...) // local context in bytes
input = append(input, uint32ToBytes32(uint32(len(wit.State)))...) // state data length in bytes
input = append(input, abiState[:]...)
input = append(input, uint32ToBytes32(uint32(len(wit.MemProof)))...) // proof data length in bytes
input = append(input, wit.MemProof[:]...)
return input
}
func (wit *StepWitness) HasPreimage() bool {
return wit.PreimageKey != ([32]byte{})
}
func (wit *StepWitness) EncodePreimageOracleInput(localContext LocalContext) ([]byte, error) {
if wit.PreimageKey == ([32]byte{}) {
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
}
switch preimage.KeyType(wit.PreimageKey[0]) {
case preimage.LocalKeyType:
if len(wit.PreimageValue) > 32+8 {
return nil, fmt.Errorf("local pre-image exceeds maximum size of 32 bytes with key 0x%x", wit.PreimageKey)
}
var input []byte
input = append(input, LoadLocalDataBytes4...)
input = append(input, wit.PreimageKey[:]...)
input = append(input, uint64ToBytes32(uint64(localContext))...) // local context in bytes
preimagePart := wit.PreimageValue[8:]
var tmp [32]byte
copy(tmp[:], preimagePart)
input = append(input, tmp[:]...)
input = append(input, uint32ToBytes32(uint32(len(wit.PreimageValue)-8))...)
input = append(input, uint32ToBytes32(wit.PreimageOffset)...)
// Note: we can pad calldata to 32 byte multiple, but don't strictly have to
return input, nil
case preimage.Keccak256KeyType:
var input []byte
input = append(input, LoadKeccak256PreimagePartBytes4...)
input = append(input, uint32ToBytes32(wit.PreimageOffset)...)
input = append(input, uint32ToBytes32(32+32)...) // partOffset, calldata offset
input = append(input, uint32ToBytes32(uint32(len(wit.PreimageValue))-8)...)
input = append(input, wit.PreimageValue[8:]...)
// Note: we can pad calldata to 32 byte multiple, but don't strictly have to
return input, nil
default:
return nil, fmt.Errorf("unsupported pre-image type %d, cannot prepare preimage with key %x offset %d for oracle",
wit.PreimageKey[0], wit.PreimageKey, wit.PreimageOffset)
}
}
......@@ -33,7 +33,7 @@ flag_management:
individual_flags:
- name: contracts-bedrock-tests
paths:
- packages/contracts-bedrock
- packages/contracts-bedrock/src
statuses:
- type: patch
target: 100%
......
......@@ -33,16 +33,16 @@ require (
github.com/multiformats/go-multiaddr v0.12.0
github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.29.0
github.com/onsi/gomega v1.30.0
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0
github.com/prometheus/client_golang v1.17.0
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
golang.org/x/crypto v0.14.0
golang.org/x/crypto v0.15.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.5.0
golang.org/x/term v0.13.0
golang.org/x/term v0.14.0
golang.org/x/time v0.4.0
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5
......@@ -199,8 +199,8 @@ require (
go.uber.org/zap v1.26.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
......
......@@ -569,8 +569,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
......@@ -769,8 +769,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
......@@ -879,13 +879,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
......@@ -895,8 +895,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......
......@@ -51,3 +51,7 @@ export interface WithdrawalResponse {
hasNextPage: boolean;
items: WithdrawalItem[];
}
export interface BridgeSupplyView {
l1DepositSum: number /* float64 */;
l2WithdrawalSum: number /* float64 */;
}
......@@ -35,6 +35,8 @@ const (
HealthPath = "/healthz"
DepositsPath = "/api/v0/deposits/"
WithdrawalsPath = "/api/v0/withdrawals/"
SupplyPath = "/api/v0/supply"
)
// Api ... Indexer API struct
......@@ -140,15 +142,14 @@ func (a *APIService) initRouter(apiConfig config.ServerConfig) {
promRecorder := metrics.NewPromHTTPRecorder(a.metricsRegistry, MetricsNamespace)
// (2) Inject routing middleware
apiRouter.Use(chiMetricsMiddleware(promRecorder))
apiRouter.Use(middleware.Timeout(time.Duration(apiConfig.WriteTimeout) * time.Second))
apiRouter.Use(middleware.Recoverer)
apiRouter.Use(middleware.Heartbeat(HealthPath))
// (3) Set GET routes
apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
apiRouter.Get(SupplyPath, h.SupplyView)
a.router = apiRouter
}
......
......@@ -95,6 +95,14 @@ func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalsByAddress(address common.
},
}, nil
}
func (mbv *MockBridgeTransfersView) L1BridgeDepositSum() (float64, error) {
return 69, nil
}
func (mbv *MockBridgeTransfersView) L2BridgeWithdrawalSum() (float64, error) {
return 420, nil
}
func TestHealthz(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
cfg := &Config{
......
......@@ -49,6 +49,11 @@ type WithdrawalResponse struct {
Items []WithdrawalItem `json:"items"`
}
type BridgeSupplyView struct {
L1DepositSum float64 `json:"l1DepositSum"`
L2WithdrawalSum float64 `json:"l2WithdrawalSum"`
}
// FIXME make a pure function that returns a struct instead of newWithdrawalResponse
// newWithdrawalResponse ... Converts a database.L2BridgeWithdrawalsResponse to an api.WithdrawalResponse
func CreateWithdrawalResponse(withdrawals *database.L2BridgeWithdrawalsResponse) WithdrawalResponse {
......
package routes
import (
"net/http"
"github.com/ethereum-optimism/optimism/indexer/api/models"
)
// SupplyView ... Handles /api/v0/supply GET requests
func (h Routes) SupplyView(w http.ResponseWriter, r *http.Request) {
depositSum, err := h.view.L1BridgeDepositSum()
if err != nil {
http.Error(w, "internal server error reading deposits", http.StatusInternalServerError)
h.logger.Error("unable to read deposits from DB", "err", err.Error())
return
}
withdrawalSum, err := h.view.L2BridgeWithdrawalSum()
if err != nil {
http.Error(w, "internal server error reading withdrawals", http.StatusInternalServerError)
h.logger.Error("unable to read withdrawals from DB", "err", err.Error())
return
}
view := models.BridgeSupplyView{
L1DepositSum: depositSum,
L2WithdrawalSum: withdrawalSum,
}
err = jsonResponse(w, view, http.StatusOK)
if err != nil {
h.logger.Error("error writing response", "err", err)
}
}
......@@ -23,6 +23,7 @@ const (
healthz = "get_health"
deposits = "get_deposits"
withdrawals = "get_withdrawals"
sum = "get_sum"
)
// Option ... Provides configuration through callback injection
......@@ -164,6 +165,25 @@ func (c *Client) GetAllDepositsByAddress(l1Address common.Address) ([]models.Dep
}
// GetSupplyAssessment ... Returns an assessment of the current supply
// on both L1 and L2. This includes the individual sums of
// (L1/L2) deposits and withdrawals
func (c *Client) GetSupplyAssessment() (*models.BridgeSupplyView, error) {
url := c.cfg.BaseURL + api.SupplyPath
resp, err := c.doRecordRequest(sum, url)
if err != nil {
return nil, err
}
var bsv *models.BridgeSupplyView
if err := json.Unmarshal(resp, &bsv); err != nil {
return nil, err
}
return bsv, nil
}
// GetAllWithdrawalsByAddress ... Gets all withdrawals provided a L2 address
func (c *Client) GetAllWithdrawalsByAddress(l2Address common.Address) ([]models.WithdrawalItem, error) {
var withdrawals []models.WithdrawalItem
......
......@@ -37,6 +37,10 @@ func BlockHeaderFromHeader(header *types.Header) BlockHeader {
}
}
func (b BlockHeader) String() string {
return fmt.Sprintf("{Hash: %s, Number: %s}", b.Hash, b.Number)
}
type L1BlockHeader struct {
BlockHeader `gorm:"embedded"`
}
......@@ -48,13 +52,13 @@ type L2BlockHeader struct {
type BlocksView interface {
L1BlockHeader(common.Hash) (*L1BlockHeader, error)
L1BlockHeaderWithFilter(BlockHeader) (*L1BlockHeader, error)
L1BlockHeaderWithScope(func(db *gorm.DB) *gorm.DB) (*L1BlockHeader, error)
L1LatestBlockHeader() (*L1BlockHeader, error)
L2BlockHeader(common.Hash) (*L2BlockHeader, error)
L2BlockHeaderWithFilter(BlockHeader) (*L2BlockHeader, error)
L2BlockHeaderWithScope(func(db *gorm.DB) *gorm.DB) (*L2BlockHeader, error)
L2LatestBlockHeader() (*L2BlockHeader, error)
LatestObservedEpoch(*big.Int, uint64) (*Epoch, error)
}
type BlocksDB interface {
......@@ -94,8 +98,12 @@ func (db *blocksDB) L1BlockHeader(hash common.Hash) (*L1BlockHeader, error) {
}
func (db *blocksDB) L1BlockHeaderWithFilter(filter BlockHeader) (*L1BlockHeader, error) {
return db.L1BlockHeaderWithScope(func(gorm *gorm.DB) *gorm.DB { return gorm.Where(&filter) })
}
func (db *blocksDB) L1BlockHeaderWithScope(scope func(*gorm.DB) *gorm.DB) (*L1BlockHeader, error) {
var l1Header L1BlockHeader
result := db.gorm.Where(&filter).Take(&l1Header)
result := db.gorm.Scopes(scope).Take(&l1Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
......@@ -137,8 +145,12 @@ func (db *blocksDB) L2BlockHeader(hash common.Hash) (*L2BlockHeader, error) {
}
func (db *blocksDB) L2BlockHeaderWithFilter(filter BlockHeader) (*L2BlockHeader, error) {
return db.L2BlockHeaderWithScope(func(gorm *gorm.DB) *gorm.DB { return gorm.Where(&filter) })
}
func (db *blocksDB) L2BlockHeaderWithScope(scope func(*gorm.DB) *gorm.DB) (*L2BlockHeader, error) {
var l2Header L2BlockHeader
result := db.gorm.Where(&filter).Take(&l2Header)
result := db.gorm.Scopes(scope).Take(&l2Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
......@@ -161,104 +173,3 @@ func (db *blocksDB) L2LatestBlockHeader() (*L2BlockHeader, error) {
return &l2Header, nil
}
// Auxiliary Methods on both L1 & L2
type Epoch struct {
L1BlockHeader L1BlockHeader `gorm:"embedded"`
L2BlockHeader L2BlockHeader `gorm:"embedded"`
}
// LatestObservedEpoch return the marker for latest epoch, observed on L1 & L2, within
// the specified bounds. In other words this returns the latest indexed L1 block that has
// a corresponding indexed L2 block with a matching L1Origin (equal timestamps).
//
// If `fromL1Height` (inclusive) is not specified, the search will start from genesis and
// continue all the way to latest indexed heights if `maxL1Range == 0`.
//
// For more, see the protocol spec:
// - https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md
func (db *blocksDB) LatestObservedEpoch(fromL1Height *big.Int, maxL1Range uint64) (*Epoch, error) {
// We use timestamps since that translates to both L1 & L2
var fromTimestamp, toTimestamp uint64
// Lower Bound (the default `fromTimestamp = l1_starting_height` (default=0) suffices genesis representation)
var header L1BlockHeader
if fromL1Height != nil {
result := db.gorm.Where("number = ?", fromL1Height).Take(&header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
fromTimestamp = header.Timestamp
} else {
// Take the lowest indexed L1 block to compute the lower bound
result := db.gorm.Order("number ASC").Take(&header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
fromL1Height = header.Number
fromTimestamp = header.Timestamp
}
// Upper Bound (lowest timestamp indexed between L1/L2 bounded by `maxL1Range`)
{
l1QueryFilter := fmt.Sprintf("timestamp >= %d", fromTimestamp)
if maxL1Range > 0 {
maxHeight := new(big.Int).Add(fromL1Height, big.NewInt(int64(maxL1Range)))
l1QueryFilter = fmt.Sprintf("%s AND number <= %d", l1QueryFilter, maxHeight)
}
// Fetch most recent header from l1_block_headers table
var l1Header L1BlockHeader
result := db.gorm.Where(l1QueryFilter).Order("timestamp DESC").Take(&l1Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
toTimestamp = l1Header.Timestamp
// Fetch most recent header from l2_block_headers table
var l2Header L2BlockHeader
result = db.gorm.Where("timestamp <= ?", toTimestamp).Order("timestamp DESC").Take(&l2Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
if l2Header.Timestamp < toTimestamp {
toTimestamp = l2Header.Timestamp
}
}
// Search for the latest indexed epoch within range. This is a faster query than doing an INNER JOIN between
// l1_block_headers and l2_block_headers which requires a full table scan to compute the resulting table.
l1Query := db.gorm.Table("l1_block_headers").Where("timestamp >= ? AND timestamp <= ?", fromTimestamp, toTimestamp)
l2Query := db.gorm.Table("l2_block_headers").Where("timestamp >= ? AND timestamp <= ?", fromTimestamp, toTimestamp)
query := db.gorm.Raw(`SELECT * FROM (?) AS l1_block_headers, (?) AS l2_block_headers
WHERE l1_block_headers.timestamp = l2_block_headers.timestamp
ORDER BY l2_block_headers.number DESC LIMIT 1`, l1Query, l2Query)
var epoch Epoch
result := query.Take(&epoch)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &epoch, nil
}
......@@ -50,9 +50,11 @@ type L2TransactionWithdrawal struct {
type BridgeTransactionsView interface {
L1TransactionDeposit(common.Hash) (*L1TransactionDeposit, error)
L1LatestBlockHeader() (*L1BlockHeader, error)
L1LatestFinalizedBlockHeader() (*L1BlockHeader, error)
L2TransactionWithdrawal(common.Hash) (*L2TransactionWithdrawal, error)
L2LatestBlockHeader() (*L2BlockHeader, error)
L2LatestFinalizedBlockHeader() (*L2BlockHeader, error)
}
type BridgeTransactionsDB interface {
......@@ -106,23 +108,41 @@ func (db *bridgeTransactionsDB) L1TransactionDeposit(sourceHash common.Hash) (*L
}
func (db *bridgeTransactionsDB) L1LatestBlockHeader() (*L1BlockHeader, error) {
// Markers for an indexed bridge event
// L1: Latest Transaction Deposit, Latest Proven/Finalized Withdrawal
l1DepositQuery := db.gorm.Table("l1_transaction_deposits").Order("l1_transaction_deposits.timestamp DESC").Limit(1)
l1DepositQuery = l1DepositQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l1_transaction_deposits.initiated_l1_event_guid")
l1DepositQuery = l1DepositQuery.Select("l1_contract_events.*")
// Latest Transaction Deposit
l1Query := db.gorm.Table("l1_transaction_deposits").Order("l1_transaction_deposits.timestamp DESC")
l1Query = l1Query.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l1_transaction_deposits.initiated_l1_event_guid")
l1Query = l1Query.Joins("INNER JOIN l1_block_headers ON l1_block_headers.hash = l1_contract_events.block_hash")
l1Query = l1Query.Select("l1_block_headers.*")
l1ProvenQuery := db.gorm.Table("l2_transaction_withdrawals")
l1ProvenQuery = l1ProvenQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
l1ProvenQuery = l1ProvenQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*").Limit(1)
var l1Header L1BlockHeader
result := l1Query.Take(&l1Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
l1FinalizedQuery := db.gorm.Table("l2_transaction_withdrawals")
l1FinalizedQuery = l1FinalizedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
l1FinalizedQuery = l1FinalizedQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*").Limit(1)
return &l1Header, nil
}
func (db *bridgeTransactionsDB) L1LatestFinalizedBlockHeader() (*L1BlockHeader, error) {
// A Proven, Finalized Event or Relayed Message
provenQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1)
provenQuery = provenQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
provenQuery = provenQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*")
finalizedQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1)
finalizedQuery = finalizedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.finalized_l1_event_guid")
finalizedQuery = finalizedQuery.Select("l1_contract_events.*")
l1Query := db.gorm.Table("((?) UNION (?) UNION (?)) AS latest_bridge_events", l1DepositQuery.Limit(1), l1ProvenQuery, l1FinalizedQuery)
l1Query = l1Query.Joins("INNER JOIN l1_block_headers ON l1_block_headers.hash = latest_bridge_events.block_hash")
l1Query = l1Query.Order("latest_bridge_events.timestamp DESC").Select("l1_block_headers.*")
relayedQuery := db.gorm.Table("l2_bridge_messages").Order("timestamp DESC").Limit(1)
relayedQuery = relayedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_bridge_messages.relayed_message_event_guid")
relayedQuery = relayedQuery.Select("l1_contract_events.*")
l1Query := db.gorm.Table("((?) UNION (?) UNION (?)) AS finalized_bridge_events", provenQuery, finalizedQuery, relayedQuery)
l1Query = l1Query.Joins("INNER JOIN l1_block_headers ON l1_block_headers.hash = finalized_bridge_events.block_hash")
l1Query = l1Query.Order("finalized_bridge_events.timestamp DESC").Select("l1_block_headers.*")
var l1Header L1BlockHeader
result := l1Query.Take(&l1Header)
......@@ -207,47 +227,39 @@ func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalFinalizedEvent(withdr
}
func (db *bridgeTransactionsDB) L2LatestBlockHeader() (*L2BlockHeader, error) {
// L2: Latest Withdrawal, Latest L2 Header of indexed deposit epoch
var latestWithdrawalHeader, latestL2DepositHeader *L2BlockHeader
var withdrawHeader L2BlockHeader
withdrawalQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1)
withdrawalQuery = withdrawalQuery.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
withdrawalQuery = withdrawalQuery.Joins("INNER JOIN l2_block_headers ON l2_block_headers.hash = l2_contract_events.block_hash")
result := withdrawalQuery.Select("l2_block_headers.*").Take(&withdrawHeader)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
latestWithdrawalHeader = &withdrawHeader
// L2: Latest Withdrawal
l2Query := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC")
l2Query = l2Query.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
l2Query = l2Query.Joins("INNER JOIN l2_block_headers ON l2_block_headers.hash = l2_contract_events.block_hash")
l2Query = l2Query.Select("l2_block_headers.*")
var l2Header L2BlockHeader
result := l2Query.Take(&l2Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
// Check for any deposits that may have been included after the latest withdrawal. However, since the bridge
// processor only inserts entries when the corresponding epoch has been indexed on both L1 and L2, we can
// simply look for the latest L2 block with at <= time of the latest L1 deposit.
var l1Deposit L1TransactionDeposit
result = db.gorm.Table("l1_transaction_deposits").Order("timestamp DESC").Limit(1).Take(&l1Deposit)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
var l2DepositHeader L2BlockHeader
result := db.gorm.Table("l2_block_headers").Order("timestamp DESC").Limit(1).Where("timestamp <= ?", l1Deposit.Tx.Timestamp).Take(&l2DepositHeader)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
latestL2DepositHeader = &l2DepositHeader
}
}
// compare
if latestWithdrawalHeader == nil {
return latestL2DepositHeader, nil
} else if latestL2DepositHeader == nil {
return latestWithdrawalHeader, nil
}
return &l2Header, nil
}
if latestWithdrawalHeader.Timestamp >= latestL2DepositHeader.Timestamp {
return latestWithdrawalHeader, nil
} else {
return latestL2DepositHeader, nil
func (db *bridgeTransactionsDB) L2LatestFinalizedBlockHeader() (*L2BlockHeader, error) {
// Only a Relayed message since we dont track L1 deposit inclusion status.
relayedQuery := db.gorm.Table("l1_bridge_messages").Order("timestamp DESC").Limit(1)
relayedQuery = relayedQuery.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l1_bridge_messages.relayed_message_event_guid")
relayedQuery = relayedQuery.Joins("INNER JOIN l2_block_headers ON l2_block_headers.hash = l2_contract_events.block_hash")
relayedQuery = relayedQuery.Select("l2_block_headers.*")
var l2Header L2BlockHeader
result := relayedQuery.Take(&l2Header)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &l2Header, nil
}
......@@ -61,10 +61,12 @@ type L2BridgeWithdrawalWithTransactionHashes struct {
type BridgeTransfersView interface {
L1BridgeDeposit(common.Hash) (*L1BridgeDeposit, error)
L1BridgeDepositSum() (float64, error)
L1BridgeDepositWithFilter(BridgeTransfer) (*L1BridgeDeposit, error)
L1BridgeDepositsByAddress(common.Address, string, int) (*L1BridgeDepositsResponse, error)
L2BridgeWithdrawal(common.Hash) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalSum() (float64, error)
L2BridgeWithdrawalWithFilter(BridgeTransfer) (*L2BridgeWithdrawal, error)
L2BridgeWithdrawalsByAddress(common.Address, string, int) (*L2BridgeWithdrawalsResponse, error)
}
......@@ -136,6 +138,17 @@ type L1BridgeDepositsResponse struct {
HasNextPage bool
}
// L1BridgeDepositSum ... returns the sum of all l1 bridge deposit mints in gwei
func (db *bridgeTransfersDB) L1BridgeDepositSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L1TransactionDeposit{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}
return sum, nil
}
// L1BridgeDepositsByAddress retrieves a list of deposits initiated by the specified address,
// coupled with the L1/L2 transaction hashes that complete the bridge transaction.
func (db *bridgeTransfersDB) L1BridgeDepositsByAddress(address common.Address, cursor string, limit int) (*L1BridgeDepositsResponse, error) {
......@@ -233,6 +246,16 @@ func (db *bridgeTransfersDB) L2BridgeWithdrawal(txWithdrawalHash common.Hash) (*
return &withdrawal, nil
}
func (db *bridgeTransfersDB) L2BridgeWithdrawalSum() (float64, error) {
var sum float64
result := db.gorm.Model(&L2TransactionWithdrawal{}).Select("sum(amount)").Scan(&sum)
if result.Error != nil {
return 0, result.Error
}
return sum, nil
}
// L2BridgeWithdrawalWithFilter queries for a bridge withdrawal with set fields in the `BridgeTransfer` filter
func (db *bridgeTransfersDB) L2BridgeWithdrawalWithFilter(filter BridgeTransfer) (*L2BridgeWithdrawal, error) {
var withdrawal L2BridgeWithdrawal
......
package database
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"gorm.io/gorm"
"github.com/stretchr/testify/mock"
)
......@@ -27,6 +26,11 @@ func (m *MockBlocksView) L1BlockHeaderWithFilter(BlockHeader) (*L1BlockHeader, e
return args.Get(0).(*L1BlockHeader), args.Error(1)
}
func (m *MockBlocksView) L1BlockHeaderWithScope(func(*gorm.DB) *gorm.DB) (*L1BlockHeader, error) {
args := m.Called()
return args.Get(0).(*L1BlockHeader), args.Error(1)
}
func (m *MockBlocksView) L1LatestBlockHeader() (*L1BlockHeader, error) {
args := m.Called()
......@@ -48,14 +52,14 @@ func (m *MockBlocksView) L2BlockHeaderWithFilter(BlockHeader) (*L2BlockHeader, e
return args.Get(0).(*L2BlockHeader), args.Error(1)
}
func (m *MockBlocksView) L2LatestBlockHeader() (*L2BlockHeader, error) {
func (m *MockBlocksView) L2BlockHeaderWithScope(func(*gorm.DB) *gorm.DB) (*L2BlockHeader, error) {
args := m.Called()
return args.Get(0).(*L2BlockHeader), args.Error(1)
return args.Get(0).(*L2BlockHeader), args.Error(2)
}
func (m *MockBlocksView) LatestObservedEpoch(*big.Int, uint64) (*Epoch, error) {
func (m *MockBlocksView) L2LatestBlockHeader() (*L2BlockHeader, error) {
args := m.Called()
return args.Get(0).(*Epoch), args.Error(1)
return args.Get(0).(*L2BlockHeader), args.Error(1)
}
type MockBlocksDB struct {
......
......@@ -43,7 +43,7 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
return l1Header != nil && l1Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil
}))
......@@ -77,7 +77,7 @@ func TestE2EBridgeL1CrossDomainMessenger(t *testing.T) {
l2DepositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, transaction.L2TransactionHash)
require.NoError(t, err)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL2Header
return l2Header != nil && l2Header.Number.Uint64() >= l2DepositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -130,7 +130,7 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastL2Header
return l2Header != nil && l2Header.Number.Uint64() >= sentMsgReceipt.BlockNumber.Uint64(), nil
}))
......@@ -159,7 +159,7 @@ func TestE2EBridgeL2CrossDomainMessenger(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizedReceipt.BlockNumber.Uint64(), nil
}))
......
......@@ -49,7 +49,7 @@ func TestE2EBridgeTransactionsOptimismPortalDeposits(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -104,7 +104,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastL2Header
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
......@@ -134,7 +134,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
withdrawParams, proveReceipt := op_e2e.ProveWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL1Header
return l1Header != nil && l1Header.Number.Uint64() >= proveReceipt.BlockNumber.Uint64(), nil
}))
......@@ -152,7 +152,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserWithdrawal(t *testing.T) {
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpCfg.Secrets.Alice, proveReceipt, withdrawParams)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......@@ -194,7 +194,7 @@ func TestE2EBridgeTransactionsL2ToL1MessagePasserFailedWithdrawal(t *testing.T)
// Prove&Finalize withdrawal
_, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......
......@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/indexer/bigint"
e2etest_utils "github.com/ethereum-optimism/optimism/indexer/e2e_tests/utils"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions"
......@@ -47,7 +48,7 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -81,7 +82,7 @@ func TestE2EBridgeTransfersStandardBridgeETHDeposit(t *testing.T) {
l2DepositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, types.NewTx(depositInfo.DepositTx).Hash())
require.NoError(t, err)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL2Header
return l2Header != nil && l2Header.Number.Uint64() >= l2DepositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -114,7 +115,7 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
return l1Header != nil && l1Header.Number.Uint64() >= portalDepositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -140,7 +141,7 @@ func TestE2EBridgeTransfersOptimismPortalETHReceive(t *testing.T) {
l2DepositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, types.NewTx(depositInfo.DepositTx).Hash())
require.NoError(t, err)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL2Header
return l2Header != nil && l2Header.Number.Uint64() >= l2DepositReceipt.BlockNumber.Uint64(), nil
}))
......@@ -182,7 +183,7 @@ func TestE2EBridgeTransfersCursoredDeposits(t *testing.T) {
// wait for processor catchup of the latest tx
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
return l1Header != nil && l1Header.Number.Uint64() >= depositReceipts[2].BlockNumber.Uint64(), nil
}))
......@@ -250,7 +251,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastL2Header
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipt.BlockNumber.Uint64(), nil
}))
......@@ -288,7 +289,7 @@ func TestE2EBridgeTransfersStandardBridgeETHWithdrawal(t *testing.T) {
// wait for processor catchup
proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, withdrawReceipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......@@ -334,7 +335,7 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserETHReceive(t *testing.T) {
// wait for processor catchup
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastL2Header
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
}))
......@@ -367,7 +368,7 @@ func TestE2EBridgeTransfersL2ToL1MessagePasserETHReceive(t *testing.T) {
// wait for processor catchup
proveReceipt, finalizeReceipt := op_e2e.ProveAndFinalizeWithdrawal(t, *testSuite.OpCfg, testSuite.L1Client, testSuite.OpSys.EthInstances["sequencer"], testSuite.OpCfg.Secrets.Alice, l2ToL1WithdrawReceipt)
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l1Header := testSuite.Indexer.BridgeProcessor.LatestL1Header
l1Header := testSuite.Indexer.BridgeProcessor.LastFinalizedL1Header
return l1Header != nil && l1Header.Number.Uint64() >= finalizeReceipt.BlockNumber.Uint64(), nil
}))
......@@ -410,7 +411,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
// wait for processor catchup of the latest tx
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
l2Header := testSuite.Indexer.BridgeProcessor.LastL2Header
return l2Header != nil && l2Header.Number.Uint64() >= withdrawReceipts[2].BlockNumber.Uint64(), nil
}))
......@@ -447,7 +448,7 @@ func TestE2EBridgeTransfersCursoredWithdrawals(t *testing.T) {
}
}
func TestClientGetWithdrawals(t *testing.T) {
func TestClientBridgeFunctions(t *testing.T) {
testSuite := createE2ETestSuite(t)
// (1) Generate contract bindings for the L1 and L2 standard bridges
......@@ -459,12 +460,16 @@ func TestClientGetWithdrawals(t *testing.T) {
// (2) Create test actors that will deposit and withdraw using the standard bridge
aliceAddr := testSuite.OpCfg.Secrets.Addresses().Alice
bobAddr := testSuite.OpCfg.Secrets.Addresses().Bob
malAddr := testSuite.OpCfg.Secrets.Addresses().Mallory
type actor struct {
addr common.Address
priv *ecdsa.PrivateKey
}
mintSum := bigint.Zero
withdrawSum := bigint.Zero
actors := []actor{
{
addr: aliceAddr,
......@@ -474,6 +479,10 @@ func TestClientGetWithdrawals(t *testing.T) {
addr: bobAddr,
priv: testSuite.OpCfg.Secrets.Bob,
},
{
addr: malAddr,
priv: testSuite.OpCfg.Secrets.Mallory,
},
}
// (3) Iterate over each actor and deposit / withdraw
......@@ -488,21 +497,28 @@ func TestClientGetWithdrawals(t *testing.T) {
l1Opts.Value = l2Opts.Value
depositTx, err := optimismPortal.Receive(l1Opts)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
depositReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L1Client, depositTx.Hash())
require.NoError(t, err)
mintSum = new(big.Int).Add(mintSum, depositTx.Value())
// (3.b) Initiate withdrawal transaction via L2ToL1MessagePasser contract
l2ToL1MessagePasserWithdrawTx, err := l2ToL1MessagePasser.Receive(l2Opts)
require.NoError(t, err)
l2ToL1WithdrawReceipt, err := wait.ForReceiptOK(context.Background(), testSuite.L2Client, l2ToL1MessagePasserWithdrawTx.Hash())
require.NoError(t, err)
// (3.c) wait for indexer processor to catchup with the L2 block containing the withdrawal tx
// (3.c) wait for indexer processor to catchup with the L1 & L2 block containing the deposit & withdrawal tx
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
l2Header := testSuite.Indexer.BridgeProcessor.LatestL2Header
return l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64(), nil
l1Header := testSuite.Indexer.BridgeProcessor.LastL1Header
l2Header := testSuite.Indexer.BridgeProcessor.LastL2Header
seenL2 := l2Header != nil && l2Header.Number.Uint64() >= l2ToL1WithdrawReceipt.BlockNumber.Uint64()
seenL1 := l1Header != nil && l1Header.Number.Uint64() >= depositReceipt.BlockNumber.Uint64()
return seenL1 && seenL2, nil
}))
withdrawSum = new(big.Int).Add(withdrawSum, l2ToL1MessagePasserWithdrawTx.Value())
// (3.d) Ensure that withdrawal and deposit txs are retrievable via API
deposits, err := testSuite.Client.GetAllDepositsByAddress(actor.addr)
require.NoError(t, err)
......@@ -513,6 +529,17 @@ func TestClientGetWithdrawals(t *testing.T) {
require.NoError(t, err)
require.Len(t, withdrawals, 1)
require.Equal(t, l2ToL1MessagePasserWithdrawTx.Hash().String(), withdrawals[0].TransactionHash)
}
// (4) Ensure that supply assessment is correct
assessment, err := testSuite.Client.GetSupplyAssessment()
require.NoError(t, err)
mintFloat, _ := mintSum.Float64()
require.Equal(t, mintFloat, assessment.L1DepositSum)
withdrawFloat, _ := withdrawSum.Float64()
require.Equal(t, withdrawFloat, assessment.L2WithdrawalSum)
}
......@@ -59,10 +59,9 @@ func (etl *ETL) Start() error {
if etl.worker != nil {
return errors.New("already started")
}
etl.log.Info("starting etl...")
etl.worker = clock.NewLoopFn(clock.SystemClock, etl.tick, func() error {
etl.log.Info("shutting down batch producer")
close(etl.etlBatches) // can close the channel now, to signal to the consumer that we're done
etl.log.Info("stopped etl worker loop")
return nil
}, etl.loopInterval)
return nil
......
......@@ -21,6 +21,7 @@ import (
type L1ETL struct {
ETL
LatestHeader *types.Header
// the batch handler may do work that we can interrupt on shutdown
resourceCtx context.Context
......@@ -31,7 +32,6 @@ type L1ETL struct {
db *database.DB
mu sync.Mutex
listeners []chan interface{}
}
......@@ -102,6 +102,8 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
resCtx, resCancel := context.WithCancel(context.Background())
return &L1ETL{
ETL: etl,
LatestHeader: fromHeader,
db: db,
resourceCtx: resCtx,
resourceCancel: resCancel,
......@@ -123,32 +125,35 @@ func (l1Etl *L1ETL) Close() error {
if err := l1Etl.tasks.Wait(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to await batch handler completion: %w", err))
}
// close listeners
for i := range l1Etl.listeners {
close(l1Etl.listeners[i])
}
return result
}
func (l1Etl *L1ETL) Start() error {
l1Etl.log.Info("starting etl...")
// start ETL batch producer
if err := l1Etl.ETL.Start(); err != nil {
return fmt.Errorf("failed to start internal ETL: %w", err)
}
// start ETL batch consumer
l1Etl.tasks.Go(func() error {
for {
// Index incoming batches (only L1 blocks that have an emitted log)
batch, ok := <-l1Etl.etlBatches
if !ok {
l1Etl.log.Info("No more batches, shutting down L1 batch handler")
return nil
}
for batch := range l1Etl.etlBatches {
if err := l1Etl.handleBatch(batch); err != nil {
return fmt.Errorf("failed to handle batch, stopping L2 ETL: %w", err)
}
}
l1Etl.log.Info("no more batches, shutting down batch handler")
return nil
})
return nil
}
func (l1Etl *L1ETL) handleBatch(batch *ETLBatch) error {
// Index incoming batches (only L1 blocks that have an emitted log)
l1BlockHeaders := make([]database.L1BlockHeader, 0, len(batch.Headers))
for i := range batch.Headers {
if _, ok := batch.HeadersWithLog[batch.Headers[i].Hash()]; ok {
......@@ -195,9 +200,11 @@ func (l1Etl *L1ETL) handleBatch(batch *ETLBatch) error {
}
batch.Logger.Info("indexed batch")
l1Etl.LatestHeader = &batch.Headers[len(batch.Headers)-1]
// Notify Listeners
l1Etl.mu.Lock()
defer l1Etl.mu.Unlock()
for i := range l1Etl.listeners {
select {
case l1Etl.listeners[i] <- struct{}{}:
......@@ -206,7 +213,7 @@ func (l1Etl *L1ETL) handleBatch(batch *ETLBatch) error {
// up the previous notif
}
}
l1Etl.mu.Unlock()
return nil
}
......
......@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
......@@ -19,6 +20,7 @@ import (
type L2ETL struct {
ETL
LatestHeader *types.Header
// the batch handler may do work that we can interrupt on shutdown
resourceCtx context.Context
......@@ -27,6 +29,9 @@ type L2ETL struct {
tasks tasks.Group
db *database.DB
mu sync.Mutex
listeners []chan interface{}
}
func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, client node.EthClient,
......@@ -81,6 +86,8 @@ func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
resCtx, resCancel := context.WithCancel(context.Background())
return &L2ETL{
ETL: etl,
LatestHeader: fromHeader,
resourceCtx: resCtx,
resourceCancel: resCancel,
db: db,
......@@ -102,10 +109,16 @@ func (l2Etl *L2ETL) Close() error {
if err := l2Etl.tasks.Wait(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to await batch handler completion: %w", err))
}
// close listeners
for i := range l2Etl.listeners {
close(l2Etl.listeners[i])
}
return result
}
func (l2Etl *L2ETL) Start() error {
l2Etl.log.Info("starting etl...")
// start ETL batch producer
if err := l2Etl.ETL.Start(); err != nil {
return fmt.Errorf("failed to start internal ETL: %w", err)
......@@ -113,17 +126,13 @@ func (l2Etl *L2ETL) Start() error {
// start ETL batch consumer
l2Etl.tasks.Go(func() error {
for {
// Index incoming batches (all L2 blocks)
batch, ok := <-l2Etl.etlBatches
if !ok {
l2Etl.log.Info("No more batches, shutting down L2 batch handler")
return nil
}
for batch := range l2Etl.etlBatches {
if err := l2Etl.handleBatch(batch); err != nil {
return fmt.Errorf("failed to handle batch, stopping L2 ETL: %w", err)
}
}
l2Etl.log.Info("no more batches, shutting down batch handler")
return nil
})
return nil
}
......@@ -169,5 +178,30 @@ func (l2Etl *L2ETL) handleBatch(batch *ETLBatch) error {
}
batch.Logger.Info("indexed batch")
l2Etl.LatestHeader = &batch.Headers[len(batch.Headers)-1]
// Notify Listeners
l2Etl.mu.Lock()
defer l2Etl.mu.Unlock()
for i := range l2Etl.listeners {
select {
case l2Etl.listeners[i] <- struct{}{}:
default:
// do nothing if the listener hasn't picked
// up the previous notif
}
}
return nil
}
// Notify returns a channel that'll receive a value every time new data has
// been persisted by the L2ETL
func (l2Etl *L2ETL) Notify() <-chan interface{} {
receiver := make(chan interface{})
l2Etl.mu.Lock()
defer l2Etl.mu.Unlock()
l2Etl.listeners = append(l2Etl.listeners, receiver)
return receiver
}
......@@ -223,7 +223,7 @@ func (ix *Indexer) initL2ETL(chainConfig config.ChainConfig) error {
func (ix *Indexer) initBridgeProcessor(chainConfig config.ChainConfig) error {
bridgeProcessor, err := processors.NewBridgeProcessor(
ix.log, ix.DB, bridge.NewMetrics(ix.metricsRegistry), ix.L1ETL, chainConfig, ix.shutdown)
ix.log, ix.DB, bridge.NewMetrics(ix.metricsRegistry), ix.L1ETL, ix.L2ETL, chainConfig, ix.shutdown)
if err != nil {
return err
}
......
......@@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS l1_transaction_deposits (
-- transaction data. NOTE: `to_address` is the recipient of funds transferred in value field of the
-- L2 deposit transaction and not the amount minted on L1 from the source address. Hence the `amount`
-- column in this table does NOT indiciate the amount transferred to the recipient but instead funds
-- bridged from L1 into `from_address`.
-- column in this table does NOT indicate the amount transferred to the recipient but instead funds
-- bridged from L1 by the `from_address`.
from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL,
......
This diff is collapsed.
......@@ -74,14 +74,15 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
// extract the deposit hash from the previous TransactionDepositedEvent
portalDeposit, ok := portalDeposits[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
if !ok {
log.Error("expected TransactionDeposit preceding SentMessage event", "tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionDeposit preceding SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash.String())
} else if portalDeposit.Event.TransactionHash != sentMessage.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "deposit_tx_hash", portalDeposit.Event.TransactionHash.String(), "message_tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. deposit_tx_hash = %s, message_tx_hash = %s", portalDeposit.Event.TransactionHash, sentMessage.Event.TransactionHash)
}
bridgeMessages[i] = database.L1BridgeMessage{TransactionSourceHash: portalDeposit.DepositTx.SourceHash, BridgeMessage: sentMessage.BridgeMessage}
bridgeMessages[i] = database.L1BridgeMessage{
TransactionSourceHash: portalDeposit.DepositTx.SourceHash,
BridgeMessage: sentMessage.BridgeMessage,
}
}
if len(bridgeMessages) > 0 {
if err := db.BridgeMessages.StoreL1BridgeMessages(bridgeMessages); err != nil {
......@@ -107,20 +108,16 @@ func L1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
// extract the cross domain message hash & deposit source hash from the following events
portalDeposit, ok := portalDeposits[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 1}]
if !ok {
log.Error("expected TransactionDeposit following BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionDeposit following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionDeposit following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if portalDeposit.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "deposit_tx_hash", portalDeposit.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch, bridge_tx_hash = %s, deposit_tx_hash = %s", initiatedBridge.Event.TransactionHash, portalDeposit.Event.TransactionHash)
}
sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 2}]
if !ok {
log.Error("expected SentMessage following BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if sentMessage.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "message_tx_hash", sentMessage.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, message_tx_hash = %s", initiatedBridge.Event.TransactionHash, sentMessage.Event.TransactionHash)
}
bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
......@@ -164,13 +161,11 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
if err != nil {
return err
} else if withdrawal == nil {
log.Error("missing indexed withdrawal on proven event!", "tx_hash", proven.Event.TransactionHash.String())
return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", proven.Event.TransactionHash.String())
return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", proven.Event.TransactionHash)
}
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(proven.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil {
log.Error("failed to mark withdrawal as proven", "err", err, "tx_hash", proven.Event.TransactionHash.String())
return err
return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", proven.Event.TransactionHash, err)
}
}
if len(provenWithdrawals) > 0 {
......@@ -192,13 +187,11 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
if err != nil {
return err
} else if withdrawal == nil {
log.Error("missing indexed withdrawal on finalization event!", "tx_hash", finalizedWithdrawal.Event.TransactionHash.String())
return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash: %s", finalizedWithdrawal.Event.TransactionHash.String())
return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash = %s", finalizedWithdrawal.Event.TransactionHash.String())
}
if err = db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(finalizedWithdrawal.WithdrawalHash, finalizedWithdrawal.Event.GUID, finalizedWithdrawal.Success); err != nil {
log.Error("failed to mark withdrawal as finalized", "err", err, "tx_hash", finalizedWithdrawal.Event.TransactionHash.String())
return err
return fmt.Errorf("failed to mark withdrawal as finalized. tx_hash = %s: %w", finalizedWithdrawal.Event.TransactionHash, err)
}
}
if len(finalizedWithdrawals) > 0 {
......@@ -220,13 +213,11 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
if err != nil {
return err
} else if message == nil {
log.Error("missing indexed L2CrossDomainMessenger message", "tx_hash", relayed.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L2CrossDomainMessager message. tx_hash %s", relayed.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L2CrossDomainMessager message! tx_hash = %s", relayed.Event.TransactionHash.String())
}
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayed.MessageHash, relayed.Event.GUID); err != nil {
log.Error("failed to relay cross domain message", "err", err, "tx_hash", relayed.Event.TransactionHash.String())
return err
return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayed.Event.TransactionHash, err)
}
}
if len(crossDomainRelayedMessages) > 0 {
......
......@@ -72,11 +72,9 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
// extract the withdrawal hash from the previous MessagePassed event
messagePassed, ok := messagesPassed[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
if !ok {
log.Error("expected MessagePassedEvent preceding SentMessage", "tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("expected MessagePassedEvent preceding SentMessage. tx_hash = %s", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("expected MessagePassedEvent preceding SentMessage. tx_hash = %s", sentMessage.Event.TransactionHash)
} else if messagePassed.Event.TransactionHash != sentMessage.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "withdraw_tx_hash", messagePassed.Event.TransactionHash.String(), "message_tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. message_tx_hash = %s, withdraw_tx_hash = %s", sentMessage.Event.TransactionHash, messagePassed.Event.TransactionHash)
}
bridgeMessages[i] = database.L2BridgeMessage{TransactionWithdrawalHash: messagePassed.WithdrawalHash, BridgeMessage: sentMessage.BridgeMessage}
......@@ -105,20 +103,16 @@ func L2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
// extract the cross domain message hash & withdraw hash from the following events
messagePassed, ok := messagesPassed[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 1}]
if !ok {
log.Error("expected MessagePassed following BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected MessagePassed following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected MessagePassed following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if messagePassed.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "withdraw_tx_hash", messagePassed.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, withdraw_tx_hash = %s", initiatedBridge.Event.TransactionHash, messagePassed.Event.TransactionHash)
}
sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex + 2}]
if !ok {
log.Error("expected SentMessage following BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage following BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if sentMessage.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "message_tx_hash", sentMessage.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, message_tx_hash = %s", initiatedBridge.Event.TransactionHash, sentMessage.Event.TransactionHash)
}
bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
......@@ -164,13 +158,11 @@ func L2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L2M
if err != nil {
return err
} else if message == nil {
log.Error("missing indexed L1CrossDomainMessenger message", "tx_hash", relayed.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L1CrossDomainMessager message. tx_hash = %s", relayed.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L1CrossDomainMessager message! tx_hash = %s", relayed.Event.TransactionHash)
}
if err := db.BridgeMessages.MarkRelayedL1BridgeMessage(relayed.MessageHash, relayed.Event.GUID); err != nil {
log.Error("failed to relay cross domain message", "err", err, "tx_hash", relayed.Event.TransactionHash.String())
return err
return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayed.Event.TransactionHash, err)
}
}
if len(crossDomainRelayedMessages) > 0 {
......
......@@ -75,11 +75,9 @@ func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// extract the deposit hash from the previous TransactionDepositedEvent
ctcTxDeposit, ok := ctcTxDeposits[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
if !ok {
log.Error("expected TransactionEnqueued preceding SentMessage event", "tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionEnqueued preceding SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionEnqueued preceding SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash)
} else if ctcTxDeposit.Event.TransactionHash != sentMessage.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "deposit_tx_hash", ctcTxDeposit.Event.TransactionHash.String(), "message_tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. deposit_tx_hash = %s, message_tx_hash = %s", ctcTxDeposit.Event.TransactionHash, sentMessage.Event.TransactionHash)
}
bridgeMessages[i] = database.L1BridgeMessage{TransactionSourceHash: ctcTxDeposit.TxHash, BridgeMessage: sentMessage.BridgeMessage}
......@@ -110,20 +108,16 @@ func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// - Event Flow: TransactionEnqueued -> SentMessage -> DepositInitiated
sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 1}]
if !ok {
log.Error("expected SentMessage preceding BridgeInitiated event", "tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage preceding DepositInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage preceding DepositInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if sentMessage.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "message_tx_hash", sentMessage.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, message_tx_hash = %s", initiatedBridge.Event.TransactionHash, sentMessage.Event.TransactionHash)
}
ctcTxDeposit, ok := ctcTxDeposits[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 2}]
if !ok {
log.Error("expected TransactionEnqueued preceding BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionEnqueued preceding BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionEnqueued preceding BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if ctcTxDeposit.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "deposit_tx_hash", ctcTxDeposit.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, deposit_tx_hash = %s", initiatedBridge.Event.TransactionHash, ctcTxDeposit.Event.TransactionHash)
}
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
......@@ -227,11 +221,9 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// - Event Flow: TransactionEnqueued -> SentMessage -> DepositInitiated
sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 1}]
if !ok {
log.Error("expected SentMessage preceding BridgeInitiated event", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage preceding BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
} else if sentMessage.Event.TransactionHash != initiatedBridge.Event.TransactionHash {
log.Error("correlated events tx hash mismatch", "message_tx_hash", sentMessage.Event.TransactionHash.String(), "bridge_tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("correlated events tx hash mismatch")
return fmt.Errorf("correlated events tx hash mismatch. bridge_tx_hash = %s, message_tx_hash = %s", initiatedBridge.Event.TransactionHash, sentMessage.Event.TransactionHash)
}
bridgedTokens[initiatedBridge.BridgeTransfer.TokenPair.LocalTokenAddress]++
......@@ -282,18 +274,15 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
// for OP-Mainnet pre-regensis withdrawals that no longer exist on L2.
tx, err := l1Client.TxByHash(relayedMessage.Event.TransactionHash)
if err != nil {
log.Error("unable to query legacy relayed tx", "tx_hash", relayedMessage.Event.TransactionHash.String(), "err", err)
return fmt.Errorf("unable to query legacy relayed tx_hash = %s: %w", relayedMessage.Event.TransactionHash.String(), err)
return fmt.Errorf("unable to query legacy relayed. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
} else if tx == nil {
log.Error("missing tx for relayed message", "tx_hash", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("missing tx for relayed message. tx_hash = %s", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("missing tx for relayed message! tx_hash = %s", relayedMessage.Event.TransactionHash)
}
relayMessageData := tx.Data()[4:]
inputs, err := contracts.CrossDomainMessengerLegacyRelayMessageEncoding.Inputs.Unpack(relayMessageData)
if err != nil || inputs == nil {
log.Error("failed to extract XDomainCallData from relayMessage transaction", "err", err, "tx_hash", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("unable to extract XDomainCallData from relayMessage transaction. err = %w. tx_hash = %s", err, relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("unable to extract XDomainCallData from relayMessage transaction. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
// NOTE: Since OP-Mainnet is the only network to go through a regensis we can simply harcode the
......@@ -304,23 +293,19 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
skippedPreRegenesisMessages++
continue
} else {
log.Error("missing indexed legacy L2CrossDomainMessenger message", "tx_hash", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L2CrossDomainMessager message. tx_hash %s", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L2CrossDomainMessenger message! tx_hash = %s", relayedMessage.Event.TransactionHash)
}
}
// Mark the associated tx withdrawal as proven/finalized with the same event. The message hash is also the transaction withdrawal hash
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
log.Error("failed to mark withdrawal as proven", "err", err)
return err
return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(relayedMessage.MessageHash, relayedMessage.Event.GUID, true); err != nil {
log.Error("failed to mark withdrawal as finalzed", "err", err)
return err
return fmt.Errorf("failed to mark withdrawal as finalized. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
log.Error("failed to relay cross domain message", "err", err)
return err
return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
}
if len(crossDomainRelayedMessages) > 0 {
......@@ -364,13 +349,11 @@ func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
if err != nil {
return err
} else if message == nil {
log.Error("missing indexed legacy L1CrossDomainMessenger message", "tx_hash", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L1CrossDomainMessager message. tx_hash = %s", relayedMessage.Event.TransactionHash.String())
return fmt.Errorf("missing indexed L1CrossDomainMessager message! tx_hash = %s", relayedMessage.Event.TransactionHash)
}
if err := db.BridgeMessages.MarkRelayedL1BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
log.Error("failed to relay cross domain message", "err", err)
return err
return fmt.Errorf("failed to relay cross domain message: %w", err)
}
}
if len(crossDomainRelayedMessages) > 0 {
......
......@@ -14,7 +14,9 @@ var (
)
type L1Metricer interface {
RecordLatestIndexedL1Height(height *big.Int)
RecordL1Interval() (done func(err error))
RecordL1LatestHeight(height *big.Int)
RecordL1LatestFinalizedHeight(height *big.Int)
RecordL1TransactionDeposits(size int, mintedETH float64)
RecordL1ProvenWithdrawals(size int)
......@@ -28,7 +30,9 @@ type L1Metricer interface {
}
type L2Metricer interface {
RecordLatestIndexedL2Height(height *big.Int)
RecordL2Interval() (done func(err error))
RecordL2LatestHeight(height *big.Int)
RecordL2LatestFinalizedHeight(height *big.Int)
RecordL2TransactionWithdrawals(size int, withdrawnETH float64)
......@@ -42,17 +46,14 @@ type L2Metricer interface {
type Metricer interface {
L1Metricer
L2Metricer
RecordInterval() (done func(err error))
}
type bridgeMetrics struct {
intervalTick prometheus.Counter
intervalDuration prometheus.Histogram
intervalFailures prometheus.Counter
latestHeight *prometheus.GaugeVec
latestL1Height prometheus.Gauge
latestL2Height prometheus.Gauge
intervalTick *prometheus.CounterVec
intervalDuration *prometheus.HistogramVec
intervalFailures *prometheus.CounterVec
txDeposits prometheus.Counter
txMintedETH prometheus.Counter
......@@ -71,32 +72,34 @@ type bridgeMetrics struct {
func NewMetrics(registry *prometheus.Registry) Metricer {
factory := metrics.With(registry)
return &bridgeMetrics{
intervalTick: factory.NewCounter(prometheus.CounterOpts{
intervalTick: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "intervals_total",
Help: "number of times processing loop has run",
}, []string{
"chain",
}),
intervalDuration: factory.NewHistogram(prometheus.HistogramOpts{
intervalDuration: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: MetricsNamespace,
Name: "interval_seconds",
Help: "duration elapsed in the processing loop",
}, []string{
"chain",
}),
intervalFailures: factory.NewCounter(prometheus.CounterOpts{
intervalFailures: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "interval_failures_total",
Help: "number of failures encountered",
}, []string{
"chain",
}),
latestL1Height: factory.NewGauge(prometheus.GaugeOpts{
latestHeight: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Subsystem: "l1",
Name: "height",
Help: "the latest processed l1 block height",
}),
latestL2Height: factory.NewGauge(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Subsystem: "l2",
Name: "height",
Help: "the latest processed l2 block height",
}, []string{
"chain",
"kind",
}),
txDeposits: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
......@@ -161,21 +164,25 @@ func NewMetrics(registry *prometheus.Registry) Metricer {
}
}
func (m *bridgeMetrics) RecordInterval() func(error) {
m.intervalTick.Inc()
timer := prometheus.NewTimer(m.intervalDuration)
// L1Metricer
func (m *bridgeMetrics) RecordL1Interval() func(error) {
m.intervalTick.WithLabelValues("l1").Inc()
timer := prometheus.NewTimer(m.intervalDuration.WithLabelValues("l1"))
return func(err error) {
timer.ObserveDuration()
if err != nil {
m.intervalFailures.Inc()
m.intervalFailures.WithLabelValues("l1").Inc()
}
}
}
// L1Metricer
func (m *bridgeMetrics) RecordL1LatestHeight(height *big.Int) {
m.latestHeight.WithLabelValues("l1", "initiated").Set(float64(height.Uint64()))
}
func (m *bridgeMetrics) RecordLatestIndexedL1Height(height *big.Int) {
m.latestL1Height.Set(float64(height.Uint64()))
func (m *bridgeMetrics) RecordL1LatestFinalizedHeight(height *big.Int) {
m.latestHeight.WithLabelValues("l1", "finalized").Set(float64(height.Uint64()))
}
func (m *bridgeMetrics) RecordL1TransactionDeposits(size int, mintedETH float64) {
......@@ -209,8 +216,23 @@ func (m *bridgeMetrics) RecordL1FinalizedBridgeTransfers(tokenAddr common.Addres
// L2Metricer
func (m *bridgeMetrics) RecordLatestIndexedL2Height(height *big.Int) {
m.latestL2Height.Set(float64(height.Uint64()))
func (m *bridgeMetrics) RecordL2Interval() func(error) {
m.intervalTick.WithLabelValues("l2").Inc()
timer := prometheus.NewTimer(m.intervalDuration.WithLabelValues("l2"))
return func(err error) {
timer.ObserveDuration()
if err != nil {
m.intervalFailures.WithLabelValues("l2").Inc()
}
}
}
func (m *bridgeMetrics) RecordL2LatestHeight(height *big.Int) {
m.latestHeight.WithLabelValues("l2", "initiated").Set(float64(height.Uint64()))
}
func (m *bridgeMetrics) RecordL2LatestFinalizedHeight(height *big.Int) {
m.latestHeight.WithLabelValues("l2", "finalized").Set(float64(height.Uint64()))
}
func (m *bridgeMetrics) RecordL2TransactionWithdrawals(size int, withdrawnETH float64) {
......
......@@ -39,13 +39,13 @@ type RollupClient interface {
type DriverSetup struct {
Log log.Logger
Metr metrics.Metricer
RollupCfg *rollup.Config
Cfg BatcherConfig
RollupConfig *rollup.Config
Config BatcherConfig
Txmgr txmgr.TxManager
L1Client L1Client
L2Client L2Client
RollupClient RollupClient
Channel ChannelConfig
ChannelConfig ChannelConfig
}
// BatchSubmitter encapsulates a service responsible for submitting L2 tx
......@@ -74,7 +74,7 @@ type BatchSubmitter struct {
func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter {
return &BatchSubmitter{
DriverSetup: setup,
state: NewChannelManager(setup.Log, setup.Metr, setup.Channel, setup.RollupCfg),
state: NewChannelManager(setup.Log, setup.Metr, setup.ChannelConfig, setup.RollupConfig),
}
}
......@@ -171,7 +171,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error {
latestBlock = block
}
l2ref, err := derive.L2BlockToBlockRef(latestBlock, &l.RollupCfg.Genesis)
l2ref, err := derive.L2BlockToBlockRef(latestBlock, &l.RollupConfig.Genesis)
if err != nil {
l.Log.Warn("Invalid L2 block loaded into state", "err", err)
return err
......@@ -183,7 +183,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error {
// loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded.
func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (*types.Block, error) {
ctx, cancel := context.WithTimeout(ctx, l.Cfg.NetworkTimeout)
ctx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout)
defer cancel()
block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber))
if err != nil {
......@@ -201,7 +201,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin
// calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state.
// It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions)
func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) {
ctx, cancel := context.WithTimeout(ctx, l.Cfg.NetworkTimeout)
ctx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout)
defer cancel()
syncStatus, err := l.RollupClient.SyncStatus(ctx)
// Ensure that we have the sync status
......@@ -244,11 +244,11 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.
func (l *BatchSubmitter) loop() {
defer l.wg.Done()
ticker := time.NewTicker(l.Cfg.PollInterval)
ticker := time.NewTicker(l.Config.PollInterval)
defer ticker.Stop()
receiptsCh := make(chan txmgr.TxReceipt[txData])
queue := txmgr.NewQueue[txData](l.killCtx, l.Txmgr, l.Cfg.MaxPendingTransactions)
queue := txmgr.NewQueue[txData](l.killCtx, l.Txmgr, l.Config.MaxPendingTransactions)
for {
select {
......@@ -347,7 +347,7 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txDat
}
candidate := txmgr.TxCandidate{
To: &l.RollupCfg.BatchInboxAddress,
To: &l.RollupConfig.BatchInboxAddress,
TxData: data,
GasLimit: intrinsicGas,
}
......@@ -387,7 +387,7 @@ func (l *BatchSubmitter) recordConfirmedTx(id txID, receipt *types.Receipt) {
// l1Tip gets the current L1 tip as a L1BlockRef. The passed context is assumed
// to be a lifetime context, so it is internally wrapped with a network timeout.
func (l *BatchSubmitter) l1Tip(ctx context.Context) (eth.L1BlockRef, error) {
tctx, cancel := context.WithTimeout(ctx, l.Cfg.NetworkTimeout)
tctx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout)
defer cancel()
head, err := l.L1Client.HeaderByNumber(tctx, nil)
if err != nil {
......
......@@ -48,7 +48,7 @@ type BatcherService struct {
RollupConfig *rollup.Config
// Channel builder parameters
Channel ChannelConfig
ChannelConfig ChannelConfig
driver *BatchSubmitter
......@@ -90,7 +90,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string,
if err := bs.initRPCClients(ctx, cfg); err != nil {
return err
}
if err := bs.initRollupCfg(ctx); err != nil {
if err := bs.initRollupConfig(ctx); err != nil {
return fmt.Errorf("failed to load rollup config: %w", err)
}
if err := bs.initChannelConfig(cfg); err != nil {
......@@ -153,12 +153,12 @@ func (bs *BatcherService) initBalanceMonitor(cfg *CLIConfig) {
}
}
func (bs *BatcherService) initRollupCfg(ctx context.Context) error {
rollupCfg, err := bs.RollupNode.RollupConfig(ctx)
func (bs *BatcherService) initRollupConfig(ctx context.Context) error {
rollupConfig, err := bs.RollupNode.RollupConfig(ctx)
if err != nil {
return fmt.Errorf("failed to retrieve rollup config: %w", err)
}
bs.RollupConfig = rollupCfg
bs.RollupConfig = rollupConfig
if err := bs.RollupConfig.Check(); err != nil {
return fmt.Errorf("invalid rollup config: %w", err)
}
......@@ -166,7 +166,7 @@ func (bs *BatcherService) initRollupCfg(ctx context.Context) error {
}
func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error {
bs.Channel = ChannelConfig{
bs.ChannelConfig = ChannelConfig{
SeqWindowSize: bs.RollupConfig.SeqWindowSize,
ChannelTimeout: bs.RollupConfig.ChannelTimeout,
MaxChannelDuration: cfg.MaxChannelDuration,
......@@ -175,7 +175,7 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error {
CompressorConfig: cfg.CompressorConfig.Config(),
BatchType: cfg.BatchType,
}
if err := bs.Channel.Check(); err != nil {
if err := bs.ChannelConfig.Check(); err != nil {
return fmt.Errorf("invalid channel configuration: %w", err)
}
return nil
......@@ -227,13 +227,13 @@ func (bs *BatcherService) initDriver() {
bs.driver = NewBatchSubmitter(DriverSetup{
Log: bs.Log,
Metr: bs.Metrics,
RollupCfg: bs.RollupConfig,
Cfg: bs.BatcherConfig,
RollupConfig: bs.RollupConfig,
Config: bs.BatcherConfig,
Txmgr: bs.TxManager,
L1Client: bs.L1Client,
L2Client: bs.L2Client,
RollupClient: bs.RollupNode,
Channel: bs.Channel,
ChannelConfig: bs.ChannelConfig,
})
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -13,7 +13,7 @@ const StorageSetterStorageLayoutJSON = "{\"storage\":null,\"types\":{}}"
var StorageSetterStorageLayout = new(solc.StorageLayout)
var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a6ed563e1161005b578063a6ed563e1461011c578063bd02d0f51461011c578063ca446dd914610138578063e2a4853a146100bf57600080fd5b806321f8a721146100825780634e91db08146100bf57806354fd4d50146100d3575b600080fd5b610095610090366004610156565b610146565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d16100cd36600461016f565b9055565b005b61010f6040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b69190610191565b61012a610090366004610156565b6040519081526020016100b6565b6100d16100cd366004610204565b6000610150825490565b92915050565b60006020828403121561016857600080fd5b5035919050565b6000806040838503121561018257600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156101be578581018301518582016040015282016101a2565b818111156101d0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561021757600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461024257600080fd5b80915050925092905056fea164736f6c634300080f000a"
var StorageSetterDeployedBin = "0x608060405234801561001057600080fd5b50600436106100885760003560e01c8063a6ed563e1161005b578063a6ed563e1461013a578063bd02d0f51461013a578063ca446dd914610156578063e2a4853a146100df57600080fd5b80630528afe21461008d57806321f8a721146100a25780634e91db08146100df57806354fd4d50146100f1575b600080fd5b6100a061009b3660046101d7565b610164565b005b6100b56100b036600461024c565b6101c7565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100a06100ed366004610265565b9055565b61012d6040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100d69190610287565b6101486100b036600461024c565b6040519081526020016100d6565b6100a06100ed3660046102fa565b8060005b818110156101c1576101af84848381811061018557610185610343565b905060400201600001358585848181106101a1576101a1610343565b905060400201602001359055565b806101b981610372565b915050610168565b50505050565b60006101d1825490565b92915050565b600080602083850312156101ea57600080fd5b823567ffffffffffffffff8082111561020257600080fd5b818501915085601f83011261021657600080fd5b81358181111561022557600080fd5b8660208260061b850101111561023a57600080fd5b60209290920196919550909350505050565b60006020828403121561025e57600080fd5b5035919050565b6000806040838503121561027857600080fd5b50508035926020909101359150565b600060208083528351808285015260005b818110156102b457858101830151858201604001528201610298565b818111156102c6576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561030d57600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461033857600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036103ca577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea164736f6c634300080f000a"
func init() {
if err := json.Unmarshal([]byte(StorageSetterStorageLayoutJSON), StorageSetterStorageLayout); err != nil {
......
......@@ -2,6 +2,7 @@ package hardhat
import (
"encoding/json"
"strings"
"github.com/ethereum-optimism/optimism/op-bindings/solc"
"github.com/ethereum/go-ethereum/accounts/abi"
......@@ -14,7 +15,7 @@ type Deployment struct {
Name string
Abi abi.ABI `json:"abi"`
Address common.Address `json:"address"`
Args []any `json:"args"`
Args []interface{} `json:"-"`
Bytecode hexutil.Bytes `json:"bytecode"`
DeployedBytecode hexutil.Bytes `json:"deployedBytecode"`
Devdoc json.RawMessage `json:"devdoc"`
......@@ -26,6 +27,61 @@ type Deployment struct {
Userdoc json.RawMessage `json:"userdoc"`
}
// UnmarshalJSON is a custom unmarshaler for Deployment, handling the Args field. This changed recently
// when `foundry` migrated to `alloy` types, and now the Args field within the contract artifact has
// a different serialization format.
//
// This custom unmarshaller should be removed when this is fixed upstream.
//
// Old Example:
// ```
// "args": [
//
// "0xCE9FeE676767A25feb9722986148Fcd87085a14e",
// "OVM_L1CrossDomainMessenger"
//
// ],
// ```
//
// New Example:
// ```
// "args": "[\"0x45ce2021212883d655348778aC99707d63D49aBc\",\"\\OVM_L1CrossDomainMessenger\\\"]"
// ```
func (d *Deployment) UnmarshalJSON(data []byte) error {
// Create a type alias to prevent recursion
type DeploymentAlias Deployment
// Unmarshal all fields except for `Args`
var alias DeploymentAlias
if err := json.Unmarshal(data, &alias); err != nil {
return err
}
// Unmarshal `Args` manually.
tmp := struct {
Args json.RawMessage `json:"args"`
}{}
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
// Strip the `args` string of escapes and quotes.
stripped := strings.ReplaceAll(strings.Trim(string(tmp.Args), "\""), "\\", "")
// Unmarshal the stripped version of the `args` field.
var args []interface{}
if err := json.Unmarshal([]byte(stripped), &args); err != nil {
return err
}
// Set the `Args` field in the `Deployment` to the correctly unmarshaled value
alias.Args = args
// Assign the unmarshaled alias back to the original struct
*d = Deployment(alias)
return nil
}
// Receipt represents the receipt held in a hardhat-deploy
// artifact file
type Receipt struct {
......
package main
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"math/big"
"os"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
)
func CalcBaseFee(parent eth.BlockInfo, elasticity uint64, canyonActive bool) *big.Int {
denomUint := uint64(50)
if canyonActive {
denomUint = uint64(250)
}
parentGasTarget := parent.GasLimit() / elasticity
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed() == parentGasTarget {
return new(big.Int).Set(parent.BaseFee())
}
var (
num = new(big.Int)
denom = new(big.Int)
)
if parent.GasUsed() > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed() - parentGasTarget)
num.Mul(num, parent.BaseFee())
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(denomUint))
baseFeeDelta := math.BigMax(num, common.Big1)
return num.Add(parent.BaseFee(), baseFeeDelta)
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parentGasTarget - parent.GasUsed())
num.Mul(num, parent.BaseFee())
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(denomUint))
baseFee := num.Sub(parent.BaseFee(), num)
return math.BigMax(baseFee, common.Big0)
}
}
func ManuallyEncodeReceipts(receipts types.Receipts, canyonActive bool) [][]byte {
v := uint64(1)
for _, receipt := range receipts {
if receipt.Type == types.DepositTxType {
if canyonActive {
receipt.DepositReceiptVersion = &v
} else {
receipt.DepositReceiptVersion = nil
}
}
}
var out [][]byte
for i := range receipts {
var buf bytes.Buffer
receipts.EncodeIndex(i, &buf)
out = append(out, buf.Bytes())
}
return out
}
type rawReceipts [][]byte
func (s rawReceipts) Len() int { return len(s) }
func (s rawReceipts) EncodeIndex(i int, w *bytes.Buffer) {
w.Write(s[i])
}
func HashList(list [][]byte) common.Hash {
hasher := trie.NewStackTrie(nil)
return types.DeriveSha(rawReceipts(list), hasher)
}
type L2Client interface {
BlockByNumber(context.Context, *big.Int) (*types.Block, error)
CodeAt(context.Context, common.Address, *big.Int) ([]byte, error)
InfoByNumber(context.Context, uint64) (eth.BlockInfo, error)
FetchReceipts(context.Context, common.Hash) (eth.BlockInfo, types.Receipts, error)
}
type Client struct {
*ethclient.Client
*sources.L1Client
}
type Args struct {
Number uint64
Elasticity uint64
Client L2Client
}
func ValidateReceipts(ctx Args, canyonActive bool) error {
block, err := ctx.Client.InfoByNumber(context.Background(), ctx.Number)
if err != nil {
return err
}
_, receipts, err := ctx.Client.FetchReceipts(context.Background(), block.Hash())
if err != nil {
return err
}
have := block.ReceiptHash()
want := HashList(ManuallyEncodeReceipts(receipts, canyonActive))
if have != want {
return fmt.Errorf("Receipts do not look correct. canyonActive: %v. have: %v, want: %v", canyonActive, have, want)
}
return nil
}
func Validate1559Params(ctx Args, canyonActive bool) error {
block, err := ctx.Client.InfoByNumber(context.Background(), ctx.Number)
if err != nil {
return err
}
parent, err := ctx.Client.InfoByNumber(context.Background(), ctx.Number-1)
if err != nil {
return err
}
want := CalcBaseFee(parent, ctx.Elasticity, canyonActive)
have := block.BaseFee()
if have.Cmp(want) != 0 {
return fmt.Errorf("BaseFee does not match. canyonActive: %v. have: %v, want: %v", canyonActive, have, want)
}
return nil
}
func ValidateWithdrawals(ctx Args, canyonActive bool) error {
block, err := ctx.Client.BlockByNumber(context.Background(), new(big.Int).SetUint64(ctx.Number))
if err != nil {
return err
}
if canyonActive && block.Withdrawals() == nil {
return errors.New("No nonwithdrawals in a canyon block")
} else if canyonActive && len(block.Withdrawals()) > 0 {
return errors.New("Withdrawals length is not zero in a canyon block")
} else if !canyonActive && block.Withdrawals() != nil {
return errors.New("Withdrawals in a pre-canyon block")
}
return nil
}
func ValidateCreate2Deployer(ctx Args, canyonActive bool) error {
addr := common.HexToAddress("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2")
code, err := ctx.Client.CodeAt(context.Background(), addr, new(big.Int).SetUint64(ctx.Number))
if err != nil {
return err
}
codeHash := crypto.Keccak256Hash(code)
expectedCodeHash := common.HexToHash("0xb0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2")
if canyonActive && codeHash != expectedCodeHash {
return fmt.Errorf("Canyon active but code hash does not match. have: %v, want: %v", codeHash, expectedCodeHash)
} else if !canyonActive && codeHash == expectedCodeHash {
return fmt.Errorf("Canyon not active but code hashes do match. codeHash: %v", codeHash)
}
return nil
}
// CheckActivation takes a function f which determines in a specific block follows the rules of a fork.
func CheckActivation(f func(Args, bool) error, ctx Args, forkActivated bool, valid *bool) {
if err := f(ctx, forkActivated); err != nil {
log.Error("Block did not follow fork rules", "err", err)
*valid = false
}
}
// CheckInactivation takes a function f which determines in a specific block follows the rules of a fork.
// It passes the oppose value of forkActivated & asserts that an error is returned.
func CheckInactivation(f func(Args, bool) error, ctx Args, forkActivated bool, valid *bool) {
if err := f(ctx, !forkActivated); err == nil {
log.Error("Block followed the wrong side of the fork rules")
*valid = false
}
}
func main() {
logger := log.New()
// Define the flag variables
var (
canyonActive bool
number uint64
elasticity uint64
rpcURL string
)
valid := true
// Define and parse the command-line flags
flag.BoolVar(&canyonActive, "canyon", false, "Set this flag to assert canyon behavior")
flag.Uint64Var(&number, "number", 31, "Block number to check")
flag.Uint64Var(&elasticity, "elasticity", 6, "Specify the EIP-1559 elasticity. 6 on mainnet/sepolia. 10 on goerli")
flag.StringVar(&rpcURL, "rpc-url", "http://localhost:8545", "Specify the L2 ETH RPC URL")
// Parse the command-line arguments
flag.Parse()
l2RPC, err := client.NewRPC(context.Background(), logger, rpcURL, client.WithDialBackoff(10))
if err != nil {
log.Crit("Error creating RPC", "err", err)
}
c := &rollup.Config{SeqWindowSize: 10}
l2Cfg := sources.L1ClientDefaultConfig(c, true, sources.RPCKindBasic)
sourceClient, err := sources.NewL1Client(l2RPC, logger, nil, l2Cfg)
if err != nil {
log.Crit("Error creating RPC", "err", err)
}
ethClient, err := ethclient.Dial(rpcURL)
if err != nil {
log.Crit("Error creating RPC", "err", err)
}
client := Client{ethClient, sourceClient}
ctx := Args{
Number: number,
Elasticity: elasticity,
Client: client,
}
CheckActivation(ValidateReceipts, ctx, canyonActive, &valid)
CheckInactivation(ValidateReceipts, ctx, canyonActive, &valid)
CheckActivation(Validate1559Params, ctx, canyonActive, &valid)
// Don't check in-activation for 1559 b/c at low basefees the two cannot be differentiated
CheckActivation(ValidateWithdrawals, ctx, canyonActive, &valid)
CheckInactivation(ValidateWithdrawals, ctx, canyonActive, &valid)
CheckActivation(ValidateCreate2Deployer, ctx, canyonActive, &valid)
CheckInactivation(ValidateCreate2Deployer, ctx, canyonActive, &valid)
if !valid {
os.Exit(1)
} else if canyonActive {
log.Info(fmt.Sprintf("Successfully validated block %v as a Canyon block", number))
} else {
log.Info(fmt.Sprintf("Successfully validated block %v as a Pre-Canyon block", number))
}
}
......@@ -7,12 +7,14 @@ import (
"fmt"
"math/big"
"os"
"strings"
"golang.org/x/sync/errgroup"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
......@@ -24,7 +26,15 @@ import (
"github.com/ethereum/go-ethereum/log"
)
var defaultCrossDomainMessageSender = common.HexToAddress("0x000000000000000000000000000000000000dead")
var (
defaultCrossDomainMessageSender = common.HexToAddress("0x000000000000000000000000000000000000dead")
// errInvalidInitialized represents when the initialized value is not set to the expected value.
// This is an assertion on `_initialized`. We do not care about the value of `_initializing`.
errInvalidInitialized = errors.New("invalid initialized value")
// errAlreadyInitialized represents a revert from when a contract is already initialized.
// This error is used to assert with `eth_call` on contracts that are `Initializable`
errAlreadyInitialized = errors.New("Initializable: contract is already initialized")
)
// Default script for checking that L2 has been configured correctly. This should be extended in the future
// to pull in L1 deploy artifacts and assert that the L2 state is consistent with the L1 state.
......@@ -138,7 +148,7 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
return err
}
if impl != standardImpl {
log.Warn("%s does not have the standard implementation", name)
log.Warn(name + " does not have the standard implementation")
}
implCode, err := client.CodeAt(context.Background(), impl, nil)
if err != nil {
......@@ -151,6 +161,8 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
})
// Ensure that the code is set to the proxy bytecode as expected
// This will not work against production networks where the bytecode
// has deviated from the current bytecode. We need a more reliable way to check for this.
g.Go(func() error {
proxyCode, err := client.CodeAt(context.Background(), p, nil)
if err != nil {
......@@ -172,97 +184,97 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
switch p {
case predeploys.LegacyMessagePasserAddr:
if err := checkLegacyMessagePasser(p, client); err != nil {
return err
return fmt.Errorf("LegacyMessagePasser: %w", err)
}
case predeploys.DeployerWhitelistAddr:
if err := checkDeployerWhitelist(p, client); err != nil {
return err
return fmt.Errorf("DeployerWhiteList: %w", err)
}
case predeploys.L2CrossDomainMessengerAddr:
if err := checkL2CrossDomainMessenger(p, client); err != nil {
return err
return fmt.Errorf("L2CrossDomainMessenger: %w", err)
}
case predeploys.GasPriceOracleAddr:
if err := checkGasPriceOracle(p, client); err != nil {
return err
return fmt.Errorf("GasPriceOracle: %w", err)
}
case predeploys.L2StandardBridgeAddr:
if err := checkL2StandardBridge(p, client); err != nil {
return err
return fmt.Errorf("L2StandardBridge: %w", err)
}
case predeploys.SequencerFeeVaultAddr:
if err := checkSequencerFeeVault(p, client); err != nil {
return err
return fmt.Errorf("SequencerFeeVault: %w", err)
}
case predeploys.OptimismMintableERC20FactoryAddr:
if err := checkOptimismMintableERC20Factory(p, client); err != nil {
return err
return fmt.Errorf("OptimismMintableERC20Factory: %w", err)
}
case predeploys.L1BlockNumberAddr:
if err := checkL1BlockNumber(p, client); err != nil {
return err
return fmt.Errorf("L1BlockNumber: %w", err)
}
case predeploys.L1BlockAddr:
if err := checkL1Block(p, client); err != nil {
return err
return fmt.Errorf("L1Block: %w", err)
}
case predeploys.WETH9Addr:
if err := checkWETH9(p, client); err != nil {
return err
return fmt.Errorf("WETH9: %w", err)
}
case predeploys.GovernanceTokenAddr:
if err := checkGovernanceToken(p, client); err != nil {
return err
return fmt.Errorf("GovernanceToken: %w", err)
}
case predeploys.L2ERC721BridgeAddr:
if err := checkL2ERC721Bridge(p, client); err != nil {
return err
return fmt.Errorf("L2ERC721Bridge: %w", err)
}
case predeploys.OptimismMintableERC721FactoryAddr:
if err := checkOptimismMintableERC721Factory(p, client); err != nil {
return err
return fmt.Errorf("OptimismMintableERC721Factory: %w", err)
}
case predeploys.ProxyAdminAddr:
if err := checkProxyAdmin(p, client); err != nil {
return err
return fmt.Errorf("ProxyAdmin: %w", err)
}
case predeploys.BaseFeeVaultAddr:
if err := checkBaseFeeVault(p, client); err != nil {
return err
return fmt.Errorf("BaseFeeVault: %w", err)
}
case predeploys.L1FeeVaultAddr:
if err := checkL1FeeVault(p, client); err != nil {
return err
return fmt.Errorf("L1FeeVault: %w", err)
}
case predeploys.L2ToL1MessagePasserAddr:
if err := checkL2ToL1MessagePasser(p, client); err != nil {
return err
return fmt.Errorf("L2ToL1MessagePasser: %w", err)
}
case predeploys.SchemaRegistryAddr:
if err := checkSchemaRegistry(p, client); err != nil {
return err
return fmt.Errorf("SchemaRegistry: %w", err)
}
case predeploys.EASAddr:
if err := checkEAS(p, client); err != nil {
return err
return fmt.Errorf("EAS: %w", err)
}
}
return nil
......@@ -429,6 +441,21 @@ func checkL2ERC721Bridge(addr common.Address, client *ethclient.Client) error {
return err
}
log.Info("L2ERC721Bridge", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.L2ERC721BridgeMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize")
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("L2ERC721Bridge", addr, client)
if err != nil {
......@@ -566,6 +593,21 @@ func checkOptimismMintableERC20Factory(addr common.Address, client *ethclient.Cl
return err
}
log.Info("OptimismMintableERC20Factory", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.OptimismMintableERC20FactoryMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize", common.Address{})
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("OptimismMintableERC20Factory", addr, client)
if err != nil {
......@@ -647,6 +689,21 @@ func checkL2StandardBridge(addr common.Address, client *ethclient.Client) error
return err
}
log.Info("L2StandardBridge", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.L2StandardBridgeMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize")
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("L2StandardBridge", addr, client)
if err != nil {
......@@ -766,6 +823,21 @@ func checkL2CrossDomainMessenger(addr common.Address, client *ethclient.Client)
return err
}
log.Info("L2CrossDomainMessenger", "_initialized", initialized)
if initialized.Uint64() != genesis.InitializedValue {
return fmt.Errorf("%w: %s", errInvalidInitialized, initialized)
}
abi, err := bindings.L2CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return err
}
calldata, err := abi.Pack("initialize")
if err != nil {
return err
}
if err := checkAlreadyInitialized(addr, calldata, client); err != nil {
return err
}
initializing, err := getInitializing("L2CrossDomainMessenger", addr, client)
if err != nil {
......@@ -918,3 +990,16 @@ func getStorageValue(name, entryName string, addr common.Address, client *ethcli
}
return slice[entry.Offset : entry.Offset+typ.NumberOfBytes], nil
}
// checkAlreadyInitialized will check if a contract has already been initialized
// based on error message string matching.
func checkAlreadyInitialized(addr common.Address, calldata []byte, client *ethclient.Client) error {
msg := ethereum.CallMsg{
To: &addr,
Data: calldata,
}
if _, err := client.CallContract(context.Background(), msg, nil); !strings.Contains(err.Error(), errAlreadyInitialized.Error()) {
return err
}
return nil
}
......@@ -29,7 +29,7 @@ import (
// initialzedValue represents the `Initializable` contract value. It should be kept in
// sync with the constant in `Constants.sol`.
// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/libraries/Constants.sol
const initializedValue = 3
const InitializedValue = 3
var (
ErrInvalidDeployConfig = errors.New("invalid deploy config")
......@@ -352,6 +352,12 @@ func (d *DeployConfig) Check() error {
if d.L1BlockTime < d.L2BlockTime {
return fmt.Errorf("L2 block time (%d) is larger than L1 block time (%d)", d.L2BlockTime, d.L1BlockTime)
}
if d.RequiredProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RequiredProtocolVersion is empty")
}
if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RecommendedProtocolVersion is empty")
}
return nil
}
......@@ -388,41 +394,28 @@ func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
}
// GetDeployedAddresses will get the deployed addresses of deployed L1 contracts
// required for the L2 genesis creation. Legacy systems use the `Proxy__` prefix
// while modern systems use the `Proxy` suffix. First check for the legacy
// deployments so that this works with upgrading a system.
// required for the L2 genesis creation.
func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
var err error
if d.L1StandardBridgeProxy == (common.Address{}) {
var l1StandardBridgeProxyDeployment *hardhat.Deployment
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1StandardBridge")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1StandardBridgeProxyDeployment, err = hh.GetDeployment("L1StandardBridgeProxy")
l1StandardBridgeProxyDeployment, err := hh.GetDeployment("L1StandardBridgeProxy")
if err != nil {
return err
}
return fmt.Errorf("cannot find L1StandardBridgeProxy artifact: %w", err)
}
d.L1StandardBridgeProxy = l1StandardBridgeProxyDeployment.Address
}
if d.L1CrossDomainMessengerProxy == (common.Address{}) {
var l1CrossDomainMessengerProxyDeployment *hardhat.Deployment
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("Proxy__OVM_L1CrossDomainMessenger")
if errors.Is(err, hardhat.ErrCannotFindDeployment) {
l1CrossDomainMessengerProxyDeployment, err = hh.GetDeployment("L1CrossDomainMessengerProxy")
l1CrossDomainMessengerProxyDeployment, err := hh.GetDeployment("L1CrossDomainMessengerProxy")
if err != nil {
return err
}
return fmt.Errorf("cannot find L1CrossDomainMessengerProxy artifact: %w", err)
}
d.L1CrossDomainMessengerProxy = l1CrossDomainMessengerProxyDeployment.Address
}
if d.L1ERC721BridgeProxy == (common.Address{}) {
// There is no legacy deployment of this contract
l1ERC721BridgeProxyDeployment, err := hh.GetDeployment("L1ERC721BridgeProxy")
if err != nil {
return err
return fmt.Errorf("cannot find L1ERC721BridgeProxy artifact: %w", err)
}
d.L1ERC721BridgeProxy = l1ERC721BridgeProxyDeployment.Address
}
......@@ -430,7 +423,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
if d.SystemConfigProxy == (common.Address{}) {
systemConfigProxyDeployment, err := hh.GetDeployment("SystemConfigProxy")
if err != nil {
return err
return fmt.Errorf("cannot find SystemConfigProxy artifact: %w", err)
}
d.SystemConfigProxy = systemConfigProxyDeployment.Address
}
......@@ -438,7 +431,7 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
if d.OptimismPortalProxy == (common.Address{}) {
optimismPortalProxyDeployment, err := hh.GetDeployment("OptimismPortalProxy")
if err != nil {
return err
return fmt.Errorf("cannot find OptimismPortalProxy artifact: %w", err)
}
d.OptimismPortalProxy = optimismPortalProxyDeployment.Address
}
......@@ -733,13 +726,13 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
"msgNonce": 0,
}
storage["L2CrossDomainMessenger"] = state.StorageValues{
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
"xDomainMsgSender": "0x000000000000000000000000000000000000dEaD",
"msgNonce": 0,
}
storage["L2StandardBridge"] = state.StorageValues{
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
"messenger": predeploys.L2CrossDomainMessengerAddr,
}
......@@ -774,12 +767,12 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
}
storage["L2ERC721Bridge"] = state.StorageValues{
"messenger": predeploys.L2CrossDomainMessengerAddr,
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
}
storage["OptimismMintableERC20Factory"] = state.StorageValues{
"bridge": predeploys.L2StandardBridgeAddr,
"_initialized": initializedValue,
"_initialized": InitializedValue,
"_initializing": false,
}
return storage, nil
......
......@@ -33,19 +33,17 @@ type Agent struct {
solver *solver.GameSolver
loader ClaimLoader
responder Responder
updater types.OracleUpdater
maxDepth int
agreeWithProposedOutput bool
log log.Logger
}
func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceProvider, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent {
func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceAccessor, responder Responder, agreeWithProposedOutput bool, log log.Logger) *Agent {
return &Agent{
metrics: m,
solver: solver.NewGameSolver(maxDepth, trace),
loader: loader,
responder: responder,
updater: updater,
maxDepth: maxDepth,
agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
......@@ -77,13 +75,6 @@ func (a *Agent) Act(ctx context.Context) error {
log = log.New("value", action.Value)
}
if action.OracleData != nil {
a.log.Info("Updating oracle data", "oracleKey", action.OracleData.OracleKey, "oracleData", action.OracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, action.OracleData); err != nil {
return fmt.Errorf("failed to load oracle data: %w", err)
}
}
switch action.Type {
case types.ActionTypeMove:
a.metrics.RecordGameMove()
......
......@@ -5,6 +5,7 @@ import (
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
......@@ -112,10 +113,9 @@ func setupTestAgent(t *testing.T, agreeWithProposedOutput bool) (*Agent, *stubCl
logger := testlog.Logger(t, log.LvlInfo)
claimLoader := &stubClaimLoader{}
depth := 4
trace := alphabet.NewTraceProvider("abcd", uint64(depth))
provider := alphabet.NewTraceProvider("abcd", uint64(depth))
responder := &stubResponder{}
updater := &stubUpdater{}
agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace, responder, updater, agreeWithProposedOutput, logger)
agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace.NewSimpleTraceAccessor(provider), responder, agreeWithProposedOutput, logger)
return agent, claimLoader, responder
}
......@@ -165,10 +165,3 @@ func (s *stubResponder) ResolveClaim(ctx context.Context, clainIdx uint64) error
func (s *stubResponder) PerformAction(ctx context.Context, response types.Action) error {
return nil
}
type stubUpdater struct {
}
func (s *stubUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
panic("Not implemented")
}
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
......@@ -19,6 +20,15 @@ const (
methodStatus = "status"
methodClaimCount = "claimDataLen"
methodClaim = "claimData"
methodL1Head = "l1Head"
methodProposals = "proposals"
methodResolve = "resolve"
methodResolveClaim = "resolveClaim"
methodAttack = "attack"
methodDefend = "defend"
methodStep = "step"
methodAddLocalData = "addLocalData"
methodVM = "VM"
)
type FaultDisputeGameContract struct {
......@@ -26,6 +36,12 @@ type FaultDisputeGameContract struct {
contract *batching.BoundContract
}
type Proposal struct {
Index *big.Int
L2BlockNumber *big.Int
OutputRoot common.Hash
}
func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
......@@ -62,6 +78,27 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
return result.GetHash(0), nil
}
func (f *FaultDisputeGameContract) GetL1Head(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodL1Head))
if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch L1 head: %w", err)
}
return result.GetHash(0), nil
}
// GetProposals returns the agreed and disputed proposals
func (f *FaultDisputeGameContract) GetProposals(ctx context.Context) (Proposal, Proposal, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodProposals))
if err != nil {
return Proposal{}, Proposal{}, fmt.Errorf("failed to fetch proposals: %w", err)
}
var agreed, disputed Proposal
result.GetStruct(0, &agreed)
result.GetStruct(1, &disputed)
return agreed, disputed, nil
}
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodStatus))
if err != nil {
......@@ -109,6 +146,95 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return claims, nil
}
func (f *FaultDisputeGameContract) vm(ctx context.Context) (*VMContract, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodVM))
if err != nil {
return nil, fmt.Errorf("failed to fetch VM addr: %w", err)
}
vmAddr := result.GetAddress(0)
return NewVMContract(vmAddr, f.multiCaller)
}
func (f *FaultDisputeGameContract) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodDefend, new(big.Int).SetUint64(parentContractIndex), pivot)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) {
call := f.contract.Call(methodStep, new(big.Int).SetUint64(claimIdx), isAttack, stateData, proof)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) CallResolveClaim(ctx context.Context, claimIdx uint64) error {
call := f.resolveClaimCall(claimIdx)
_, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return fmt.Errorf("failed to call resolve claim: %w", err)
}
return nil
}
func (f *FaultDisputeGameContract) ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) {
call := f.resolveClaimCall(claimIdx)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) resolveClaimCall(claimIdx uint64) *batching.ContractCall {
return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx))
}
func (f *FaultDisputeGameContract) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
call := f.resolveCall()
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, call)
if err != nil {
return gameTypes.GameStatusInProgress, fmt.Errorf("failed to call resolve: %w", err)
}
return gameTypes.GameStatusFromUint8(result.GetUint8(0))
}
func (f *FaultDisputeGameContract) ResolveTx() (txmgr.TxCandidate, error) {
call := f.resolveCall()
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
func (f *FaultDisputeGameContract) UpdateOracleTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
if data.IsLocal {
return f.addLocalDataTx(data)
}
return f.addGlobalDataTx(ctx, data)
}
func (f *FaultDisputeGameContract) addLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
call := f.contract.Call(
methodAddLocalData,
data.GetIdent(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) addGlobalDataTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
vm, err := f.vm(ctx)
if err != nil {
return txmgr.TxCandidate{}, err
}
oracle, err := vm.Oracle(ctx)
if err != nil {
return txmgr.TxCandidate{}, err
}
return oracle.AddGlobalDataTx(data)
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0)
countered := result.GetBool(1)
......
......@@ -15,6 +15,12 @@ import (
"github.com/stretchr/testify/require"
)
var (
fdgAddr = common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
vmAddr = common.HexToAddress("0x33332842371dFC380576ebb09Ae16Cb6B6c3333")
oracleAddr = common.HexToAddress("0x44442842371dFC380576ebb09Ae16Cb6B6ca4444")
)
func TestSimpleGetters(t *testing.T) {
tests := []struct {
method string
......@@ -60,12 +66,26 @@ func TestSimpleGetters(t *testing.T) {
return game.GetClaimCount(context.Background())
},
},
{
method: methodL1Head,
result: common.Hash{0xdd, 0xbb},
call: func(game *FaultDisputeGameContract) (any, error) {
return game.GetL1Head(context.Background())
},
},
{
method: methodResolve,
result: types.GameStatusInProgress,
call: func(game *FaultDisputeGameContract) (any, error) {
return game.CallResolve(context.Background())
},
},
}
for _, test := range tests {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(test.method, batching.BlockLatest, nil, []interface{}{test.result})
stubRpc.SetResponse(fdgAddr, test.method, batching.BlockLatest, nil, []interface{}{test.result})
status, err := test.call(game)
require.NoError(t, err)
expected := test.expected
......@@ -77,6 +97,33 @@ func TestSimpleGetters(t *testing.T) {
}
}
func TestGetProposals(t *testing.T) {
stubRpc, game := setup(t)
agreedIndex := big.NewInt(5)
agreedBlockNum := big.NewInt(6)
agreedRoot := common.Hash{0xaa}
disputedIndex := big.NewInt(7)
disputedBlockNum := big.NewInt(8)
disputedRoot := common.Hash{0xdd}
agreed := Proposal{
Index: agreedIndex,
L2BlockNumber: agreedBlockNum,
OutputRoot: agreedRoot,
}
disputed := Proposal{
Index: disputedIndex,
L2BlockNumber: disputedBlockNum,
OutputRoot: disputedRoot,
}
stubRpc.SetResponse(fdgAddr, methodProposals, batching.BlockLatest, []interface{}{}, []interface{}{
agreed, disputed,
})
actualAgreed, actualDisputed, err := game.GetProposals(context.Background())
require.NoError(t, err)
require.Equal(t, agreed, actualAgreed)
require.Equal(t, disputed, actualDisputed)
}
func TestGetClaim(t *testing.T) {
stubRpc, game := setup(t)
idx := big.NewInt(2)
......@@ -85,7 +132,7 @@ func TestGetClaim(t *testing.T) {
value := common.Hash{0xab}
position := big.NewInt(2)
clock := big.NewInt(1234)
stubRpc.SetResponse(methodClaim, batching.BlockLatest, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
stubRpc.SetResponse(fdgAddr, methodClaim, batching.BlockLatest, []interface{}{idx}, []interface{}{parentIndex, countered, value, position, clock})
status, err := game.GetClaim(context.Background(), idx.Uint64())
require.NoError(t, err)
require.Equal(t, faultTypes.Claim{
......@@ -133,7 +180,7 @@ func TestGetAllClaims(t *testing.T) {
ParentContractIndex: 1,
}
expectedClaims := []faultTypes.Claim{claim0, claim1, claim2}
stubRpc.SetResponse(methodClaimCount, batching.BlockLatest, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))})
stubRpc.SetResponse(fdgAddr, methodClaimCount, batching.BlockLatest, nil, []interface{}{big.NewInt(int64(len(expectedClaims)))})
for _, claim := range expectedClaims {
expectGetClaim(stubRpc, claim)
}
......@@ -142,8 +189,100 @@ func TestGetAllClaims(t *testing.T) {
require.Equal(t, expectedClaims, claims)
}
func TestCallResolveClaim(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(fdgAddr, methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
err := game.CallResolveClaim(context.Background(), 123)
require.NoError(t, err)
}
func TestResolveClaimTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(fdgAddr, methodResolveClaim, batching.BlockLatest, []interface{}{big.NewInt(123)}, nil)
tx, err := game.ResolveClaimTx(123)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestResolveTx(t *testing.T) {
stubRpc, game := setup(t)
stubRpc.SetResponse(fdgAddr, methodResolve, batching.BlockLatest, nil, nil)
tx, err := game.ResolveTx()
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestAttackTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(fdgAddr, methodAttack, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.AttackTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestDefendTx(t *testing.T) {
stubRpc, game := setup(t)
value := common.Hash{0xaa}
stubRpc.SetResponse(fdgAddr, methodDefend, batching.BlockLatest, []interface{}{big.NewInt(111), value}, nil)
tx, err := game.DefendTx(111, value)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestStepTx(t *testing.T) {
stubRpc, game := setup(t)
stateData := []byte{1, 2, 3}
proofData := []byte{4, 5, 6, 7, 8, 9}
stubRpc.SetResponse(fdgAddr, methodStep, batching.BlockLatest, []interface{}{big.NewInt(111), true, stateData, proofData}, nil)
tx, err := game.StepTx(111, true, stateData, proofData)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func TestUpdateOracleTx(t *testing.T) {
t.Run("Local", func(t *testing.T) {
stubRpc, game := setup(t)
data := &faultTypes.PreimageOracleData{
IsLocal: true,
LocalContext: 2,
OracleKey: common.Hash{0xbc}.Bytes(),
OracleData: []byte{1, 2, 3, 4, 5, 6, 7},
OracleOffset: 16,
}
stubRpc.SetResponse(fdgAddr, methodAddLocalData, batching.BlockLatest, []interface{}{
data.GetIdent(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
}, nil)
tx, err := game.UpdateOracleTx(context.Background(), data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
t.Run("Global", func(t *testing.T) {
stubRpc, game := setup(t)
data := &faultTypes.PreimageOracleData{
IsLocal: false,
OracleKey: common.Hash{0xbc}.Bytes(),
OracleData: []byte{1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15},
OracleOffset: 16,
}
stubRpc.SetResponse(fdgAddr, methodVM, batching.BlockLatest, nil, []interface{}{vmAddr})
stubRpc.SetResponse(vmAddr, methodOracle, batching.BlockLatest, nil, []interface{}{oracleAddr})
stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, batching.BlockLatest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPreimageWithoutSize(),
}, nil)
tx, err := game.UpdateOracleTx(context.Background(), data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
}
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse(
fdgAddr,
methodClaim,
batching.BlockLatest,
[]interface{}{big.NewInt(int64(claim.ContractIndex))},
......@@ -159,11 +298,17 @@ func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
func setup(t *testing.T) (*batchingTest.AbiBasedRpc, *FaultDisputeGameContract) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
address := common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAbi, address)
caller := batching.NewMultiCaller(stubRpc, 100)
game, err := NewFaultDisputeGameContract(address, caller)
vmAbi, err := bindings.MIPSMetaData.GetAbi()
require.NoError(t, err)
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi)
stubRpc.AddContract(vmAddr, vmAbi)
stubRpc.AddContract(oracleAddr, oracleAbi)
caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize)
game, err := NewFaultDisputeGameContract(fdgAddr, caller)
require.NoError(t, err)
return stubRpc, game
}
......@@ -13,6 +13,10 @@ import (
"github.com/stretchr/testify/require"
)
var (
factoryAddr = common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
)
func TestDisputeGameFactorySimpleGetters(t *testing.T) {
blockNum := uint64(23)
tests := []struct {
......@@ -35,7 +39,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
stubRpc.SetResponse(test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result})
stubRpc.SetResponse(factoryAddr, test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result})
status, err := test.call(factory)
require.NoError(t, err)
expected := test.expected
......@@ -76,6 +80,7 @@ func TestLoadGame(t *testing.T) {
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64, game types.GameMetadata) {
stubRpc.SetResponse(
factoryAddr,
methodGameAtIndex,
batching.BlockByNumber(blockNum),
[]interface{}{big.NewInt(int64(idx))},
......@@ -89,11 +94,10 @@ func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64,
func setupDisputeGameFactoryTest(t *testing.T) (*batchingTest.AbiBasedRpc, *DisputeGameFactoryContract) {
fdgAbi, err := bindings.DisputeGameFactoryMetaData.GetAbi()
require.NoError(t, err)
address := common.HexToAddress("0x24112842371dFC380576ebb09Ae16Cb6B6caD7CB")
stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAbi, address)
stubRpc := batchingTest.NewAbiBasedRpc(t, factoryAddr, fdgAbi)
caller := batching.NewMultiCaller(stubRpc, 100)
factory, err := NewDisputeGameFactoryContract(address, caller)
factory, err := NewDisputeGameFactoryContract(factoryAddr, caller)
require.NoError(t, err)
return stubRpc, factory
}
package contracts
import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
const (
methodLoadKeccak256PreimagePart = "loadKeccak256PreimagePart"
)
// PreimageOracleContract is a binding that works with contracts implementing the IPreimageOracle interface
type PreimageOracleContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller) (*PreimageOracleContract, error) {
mipsAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("failed to load preimage oracle ABI: %w", err)
}
return &PreimageOracleContract{
multiCaller: caller,
contract: batching.NewBoundContract(mipsAbi, addr),
}, nil
}
func (c PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodLoadKeccak256PreimagePart, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize())
return call.ToTxCandidate()
}
package contracts
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestPreimageOracleContract_LoadKeccak256(t *testing.T) {
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, oracleAddr, oracleAbi)
oracleContract, err := NewPreimageOracleContract(oracleAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
require.NoError(t, err)
data := &types.PreimageOracleData{
OracleKey: common.Hash{0xcc}.Bytes(),
OracleData: make([]byte, 20),
OracleOffset: 545,
}
stubRpc.SetResponse(oracleAddr, methodLoadKeccak256PreimagePart, batching.BlockLatest, []interface{}{
new(big.Int).SetUint64(uint64(data.OracleOffset)),
data.GetPreimageWithoutSize(),
}, nil)
tx, err := oracleContract.AddGlobalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
package contracts
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
)
const (
methodOracle = "oracle"
)
// VMContract is a binding that works with contracts implementing the IBigStepper interface
type VMContract struct {
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewVMContract(addr common.Address, caller *batching.MultiCaller) (*VMContract, error) {
mipsAbi, err := bindings.MIPSMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("failed to load VM ABI: %w", err)
}
return &VMContract{
multiCaller: caller,
contract: batching.NewBoundContract(mipsAbi, addr),
}, nil
}
func (c *VMContract) Oracle(ctx context.Context) (*PreimageOracleContract, error) {
results, err := c.multiCaller.SingleCall(ctx, batching.BlockLatest, c.contract.Call(methodOracle))
if err != nil {
return nil, fmt.Errorf("failed to load oracle address: %w", err)
}
return NewPreimageOracleContract(results.GetAddress(0), c.multiCaller)
}
package contracts
import (
"context"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/stretchr/testify/require"
)
func TestVMContract_Oracle(t *testing.T) {
vmAbi, err := bindings.MIPSMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, vmAddr, vmAbi)
vmContract, err := NewVMContract(vmAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
require.NoError(t, err)
stubRpc.SetResponse(vmAddr, methodOracle, batching.BlockLatest, nil, []interface{}{oracleAddr})
oracleContract, err := vmContract.Oracle(context.Background())
require.NoError(t, err)
tx, err := oracleContract.AddGlobalDataTx(&types.PreimageOracleData{
OracleData: make([]byte, 20),
})
require.NoError(t, err)
// This test doesn't care about all the tx details, we just want to confirm the contract binding is using the
// correct address
require.Equal(t, &oracleAddr, tx.To)
}
......@@ -25,6 +25,10 @@ type GameInfo interface {
GetClaimCount(context.Context) (uint64, error)
}
// gameValidator checks that the specific game instance is compatible with the configuration.
// Typically, this is done by verifying the absolute prestate of the game matches the local absolute prestate.
type gameValidator func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error
type GamePlayer struct {
act actor
agreeWithProposedOutput bool
......@@ -33,7 +37,7 @@ type GamePlayer struct {
status gameTypes.GameStatus
}
type resourceCreator func(addr common.Address, gameDepth uint64, dir string) (types.TraceProvider, types.OracleUpdater, error)
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, gameValidator, error)
func NewGamePlayer(
ctx context.Context,
......@@ -47,6 +51,7 @@ func NewGamePlayer(
creator resourceCreator,
) (*GamePlayer, error) {
logger = logger.New("game", addr)
loader, err := contracts.NewFaultDisputeGameContract(addr, batching.NewMultiCaller(client.Client(), batching.DefaultBatchSize))
if err != nil {
return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err)
......@@ -76,22 +81,23 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
provider, updater, err := creator(addr, gameDepth, dir)
accessor, validator, err := creator(addr, loader, gameDepth, dir)
if err != nil {
return nil, fmt.Errorf("failed to create trace provider: %w", err)
return nil, fmt.Errorf("failed to create trace accessor: %w", err)
}
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
if err := validator(ctx, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
responder, err := responder.NewFaultResponder(logger, txMgr, addr)
responder, err := responder.NewFaultResponder(logger, txMgr, loader)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
agent := NewAgent(m, loader, int(gameDepth), accessor, responder, cfg.AgreeWithProposedOutput, logger)
return &GamePlayer{
act: NewAgent(m, loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, logger).Act,
act: agent.Act,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
loader: loader,
logger: logger,
......
......@@ -5,6 +5,8 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
......@@ -35,17 +37,18 @@ func RegisterGameTypes(
txMgr txmgr.TxManager,
client *ethclient.Client,
) {
if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, error) {
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, client, dir, addr, gameDepth)
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr)
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, dir, gameDepth)
if err != nil {
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil {
return nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return provider, updater, nil
return trace.NewSimpleTraceAccessor(provider), validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......@@ -53,10 +56,12 @@ func RegisterGameTypes(
registry.RegisterGameType(cannonGameType, playerCreator)
}
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
resourceCreator := func(addr common.Address, gameDepth uint64, dir string) (faultTypes.TraceProvider, faultTypes.OracleUpdater, error) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
provider := alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater := alphabet.NewOracleUpdater(logger)
return provider, updater, nil
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
return trace.NewSimpleTraceAccessor(provider), validator, nil
}
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator)
......
package responder
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// setupFaultDisputeGame deploys the FaultDisputeGame contract to a simulated backend
func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.SimulatedBackend, *bindings.FaultDisputeGame, error) {
privateKey, err := crypto.GenerateKey()
from := crypto.PubkeyToAddress(privateKey.PublicKey)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{from: {Balance: big.NewInt(params.Ether)}},
50_000_000,
)
blockHashOracle, _, _, err := bindings.DeployBlockOracle(opts, backend)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
_, _, contract, err := bindings.DeployFaultDisputeGame(
opts,
backend,
uint8(0), // Game Type ID
[32]byte{0x01}, // Absolute Prestate Claim
big.NewInt(15), // Max Game Depth
uint64(604800), // 7 days
common.Address{0xdd}, // VM
common.Address{0xee}, // L2OutputOracle (Not used in Alphabet Game)
blockHashOracle, // Block hash oracle
)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
return from, opts, backend, contract, nil
}
// TestBuildFaultDefendData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultDefendData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildFaultDefendData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Defend(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultAttackData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultAttackData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildFaultAttackData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Attack(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultStepData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultStepData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
resp, _ := newTestFaultResponder(t)
data, err := resp.buildStepTxData(2, false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Step(opts, big.NewInt(2), false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
......@@ -2,153 +2,109 @@ package responder
import (
"context"
"math/big"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type GameContract interface {
CallResolve(ctx context.Context) (gameTypes.GameStatus, error)
ResolveTx() (txmgr.TxCandidate, error)
CallResolveClaim(ctx context.Context, claimIdx uint64) error
ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error)
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
UpdateOracleTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
}
// FaultResponder implements the [Responder] interface to send onchain transactions.
type FaultResponder struct {
log log.Logger
txMgr txmgr.TxManager
fdgAddr common.Address
fdgAbi *abi.ABI
contract GameContract
}
// NewFaultResponder returns a new [FaultResponder].
func NewFaultResponder(logger log.Logger, txManagr txmgr.TxManager, fdgAddr common.Address) (*FaultResponder, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, err
}
func NewFaultResponder(logger log.Logger, txMgr txmgr.TxManager, contract GameContract) (*FaultResponder, error) {
return &FaultResponder{
log: logger,
txMgr: txManagr,
fdgAddr: fdgAddr,
fdgAbi: fdgAbi,
txMgr: txMgr,
contract: contract,
}, nil
}
// buildFaultDefendData creates the transaction data for the Defend function.
func (r *FaultResponder) buildFaultDefendData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"defend",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildFaultAttackData creates the transaction data for the Attack function.
func (r *FaultResponder) buildFaultAttackData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"attack",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildResolveData creates the transaction data for the Resolve function.
func (r *FaultResponder) buildResolveData() ([]byte, error) {
return r.fdgAbi.Pack("resolve")
}
// CallResolve determines if the resolve function on the fault dispute game contract
// would succeed. Returns the game status if the call would succeed, errors otherwise.
func (r *FaultResponder) CallResolve(ctx context.Context) (gameTypes.GameStatus, error) {
txData, err := r.buildResolveData()
if err != nil {
return gameTypes.GameStatusInProgress, err
}
res, err := r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr,
Data: txData,
}, nil)
if err != nil {
return gameTypes.GameStatusInProgress, err
}
var status uint8
if err = r.fdgAbi.UnpackIntoInterface(&status, "resolve", res); err != nil {
return gameTypes.GameStatusInProgress, err
}
return gameTypes.GameStatusFromUint8(status)
return r.contract.CallResolve(ctx)
}
// Resolve executes a resolve transaction to resolve a fault dispute game.
func (r *FaultResponder) Resolve(ctx context.Context) error {
txData, err := r.buildResolveData()
candidate, err := r.contract.ResolveTx()
if err != nil {
return err
}
return r.sendTxAndWait(ctx, txData)
}
// buildResolveClaimData creates the transaction data for the ResolveClaim function.
func (r *FaultResponder) buildResolveClaimData(claimIdx uint64) ([]byte, error) {
return r.fdgAbi.Pack("resolveClaim", big.NewInt(int64(claimIdx)))
return r.sendTxAndWait(ctx, candidate)
}
// CallResolveClaim determines if the resolveClaim function on the fault dispute game contract
// would succeed.
func (r *FaultResponder) CallResolveClaim(ctx context.Context, claimIdx uint64) error {
txData, err := r.buildResolveClaimData(claimIdx)
if err != nil {
return err
}
_, err = r.txMgr.Call(ctx, ethereum.CallMsg{
To: &r.fdgAddr,
Data: txData,
}, nil)
return err
return r.contract.CallResolveClaim(ctx, claimIdx)
}
// ResolveClaim executes a resolveClaim transaction to resolve a fault dispute game.
func (r *FaultResponder) ResolveClaim(ctx context.Context, claimIdx uint64) error {
txData, err := r.buildResolveClaimData(claimIdx)
candidate, err := r.contract.ResolveClaimTx(claimIdx)
if err != nil {
return err
}
return r.sendTxAndWait(ctx, txData)
return r.sendTxAndWait(ctx, candidate)
}
func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error {
var txData []byte
if action.OracleData != nil {
r.log.Info("Updating oracle data", "key", action.OracleData.OracleKey)
candidate, err := r.contract.UpdateOracleTx(ctx, action.OracleData)
if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
}
if err := r.sendTxAndWait(ctx, candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err)
}
}
var candidate txmgr.TxCandidate
var err error
switch action.Type {
case types.ActionTypeMove:
if action.IsAttack {
txData, err = r.buildFaultAttackData(action.ParentIdx, action.Value)
candidate, err = r.contract.AttackTx(uint64(action.ParentIdx), action.Value)
} else {
txData, err = r.buildFaultDefendData(action.ParentIdx, action.Value)
candidate, err = r.contract.DefendTx(uint64(action.ParentIdx), action.Value)
}
case types.ActionTypeStep:
txData, err = r.buildStepTxData(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData)
candidate, err = r.contract.StepTx(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData)
}
if err != nil {
return err
}
return r.sendTxAndWait(ctx, txData)
return r.sendTxAndWait(ctx, candidate)
}
// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt.
// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr].
func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error {
receipt, err := r.txMgr.Send(ctx, txmgr.TxCandidate{
To: &r.fdgAddr,
TxData: txData,
GasLimit: 0,
})
func (r *FaultResponder) sendTxAndWait(ctx context.Context, candidate txmgr.TxCandidate) error {
receipt, err := r.txMgr.Send(ctx, candidate)
if err != nil {
return err
}
......@@ -159,14 +115,3 @@ func (r *FaultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
}
return nil
}
// buildStepTxData creates the transaction data for the step function.
func (r *FaultResponder) buildStepTxData(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) ([]byte, error) {
return r.fdgAbi.Pack(
"step",
big.NewInt(int64(claimIdx)),
isAttack,
stateData,
proof,
)
}
......@@ -12,7 +12,7 @@ type GameSolver struct {
claimSolver *claimSolver
}
func NewGameSolver(gameDepth int, trace types.TraceProvider) *GameSolver {
func NewGameSolver(gameDepth int, trace types.TraceAccessor) *GameSolver {
return &GameSolver{
claimSolver: newClaimSolver(gameDepth, trace),
}
......
......@@ -6,6 +6,7 @@ import (
"testing"
faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
......@@ -113,7 +114,7 @@ func TestCalculateNextActions(t *testing.T) {
i, claim.Position.ToGIndex(), claim.Position.TraceIndex(maxDepth), claim.ParentContractIndex, claim.Countered, claim.Value)
}
solver := NewGameSolver(maxDepth, claimBuilder.CorrectTraceProvider())
solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()))
actions, err := solver.CalculateNextActions(context.Background(), game)
require.NoError(t, err)
for i, action := range actions {
......
......@@ -7,7 +7,6 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
)
var (
......@@ -18,14 +17,14 @@ var (
// claimSolver uses a [TraceProvider] to determine the moves to make in a dispute game.
type claimSolver struct {
trace types.TraceProvider
trace types.TraceAccessor
gameDepth int
}
// newClaimSolver creates a new [claimSolver] using the provided [TraceProvider].
func newClaimSolver(gameDepth int, traceProvider types.TraceProvider) *claimSolver {
func newClaimSolver(gameDepth int, trace types.TraceAccessor) *claimSolver {
return &claimSolver{
traceProvider,
trace,
gameDepth,
}
}
......@@ -53,14 +52,14 @@ func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game type
}
}
agree, err := s.agreeWithClaim(ctx, claim.ClaimData)
agree, err := s.agreeWithClaim(ctx, game, claim)
if err != nil {
return nil, err
}
if agree {
return s.defend(ctx, claim)
return s.defend(ctx, game, claim)
} else {
return s.attack(ctx, claim)
return s.attack(ctx, game, claim)
}
}
......@@ -93,28 +92,25 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty
return StepData{}, ErrStepIgnoreInvalidPath
}
claimCorrect, err := s.agreeWithClaim(ctx, claim.ClaimData)
claimCorrect, err := s.agreeWithClaim(ctx, game, claim)
if err != nil {
return StepData{}, err
}
var preState []byte
var proofData []byte
var oracleData *types.PreimageOracleData
var position types.Position
if !claimCorrect {
// Attack the claim by executing step index, so we need to get the pre-state of that index
preState, proofData, oracleData, err = s.trace.GetStepData(ctx, claim.Position)
if err != nil {
return StepData{}, err
}
position = claim.Position
} else {
// We agree with the claim so Defend and use this claim as the starting point to
// execute the step after. Thus we need the pre-state of the next step.
preState, proofData, oracleData, err = s.trace.GetStepData(ctx, claim.MoveRight())
// Defend and use this claim as the starting point to execute the step after.
// Thus, we need the pre-state of the next step.
position = claim.Position.MoveRight()
}
preState, proofData, oracleData, err := s.trace.GetStepData(ctx, game, claim, position)
if err != nil {
return StepData{}, err
}
}
return StepData{
LeafClaim: claim,
......@@ -126,9 +122,9 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty
}
// attack returns a response that attacks the claim.
func (s *claimSolver) attack(ctx context.Context, claim types.Claim) (*types.Claim, error) {
func (s *claimSolver) attack(ctx context.Context, game types.Game, claim types.Claim) (*types.Claim, error) {
position := claim.Attack()
value, err := s.traceAtPosition(ctx, position)
value, err := s.trace.Get(ctx, game, claim, position)
if err != nil {
return nil, fmt.Errorf("attack claim: %w", err)
}
......@@ -139,12 +135,12 @@ func (s *claimSolver) attack(ctx context.Context, claim types.Claim) (*types.Cla
}
// defend returns a response that defends the claim.
func (s *claimSolver) defend(ctx context.Context, claim types.Claim) (*types.Claim, error) {
func (s *claimSolver) defend(ctx context.Context, game types.Game, claim types.Claim) (*types.Claim, error) {
if claim.IsRoot() {
return nil, nil
}
position := claim.Defend()
value, err := s.traceAtPosition(ctx, position)
value, err := s.trace.Get(ctx, game, claim, position)
if err != nil {
return nil, fmt.Errorf("defend claim: %w", err)
}
......@@ -155,19 +151,14 @@ func (s *claimSolver) defend(ctx context.Context, claim types.Claim) (*types.Cla
}
// agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider].
func (s *claimSolver) agreeWithClaim(ctx context.Context, claim types.ClaimData) (bool, error) {
ourValue, err := s.traceAtPosition(ctx, claim.Position)
func (s *claimSolver) agreeWithClaim(ctx context.Context, game types.Game, claim types.Claim) (bool, error) {
ourValue, err := s.trace.Get(ctx, game, claim, claim.Position)
return bytes.Equal(ourValue[:], claim.Value[:]), err
}
// traceAtPosition returns the [common.Hash] from internal [TraceProvider] at the given [Position].
func (s *claimSolver) traceAtPosition(ctx context.Context, p types.Position) (common.Hash, error) {
return s.trace.Get(ctx, p)
}
// agreeWithClaimPath returns true if the every other claim in the path to root is correct according to the internal [TraceProvider].
func (s *claimSolver) agreeWithClaimPath(ctx context.Context, game types.Game, claim types.Claim) (bool, error) {
agree, err := s.agreeWithClaim(ctx, claim.ClaimData)
agree, err := s.agreeWithClaim(ctx, game, claim)
if err != nil {
return false, err
}
......
......@@ -6,6 +6,7 @@ import (
"testing"
faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
......@@ -161,7 +162,7 @@ func TestAttemptStep(t *testing.T) {
t.Run(tableTest.name, func(t *testing.T) {
builder := claimBuilder.GameBuilder(tableTest.agreeWithOutputRoot, !tableTest.agreeWithOutputRoot)
tableTest.setupGame(builder)
alphabetSolver := newClaimSolver(maxDepth, claimBuilder.CorrectTraceProvider())
alphabetSolver := newClaimSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()))
game := builder.Game
claims := game.Claims()
lastClaim := claims[len(claims)-1]
......
......@@ -29,10 +29,14 @@ type GameBuilderSeq struct {
}
func (g *GameBuilder) Seq() *GameBuilderSeq {
return g.SeqFrom(g.Game.Claims()[0])
}
func (g *GameBuilder) SeqFrom(claim types.Claim) *GameBuilderSeq {
return &GameBuilderSeq{
gameBuilder: g,
builder: g.builder,
lastClaim: g.Game.Claims()[0],
lastClaim: claim,
}
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -3,6 +3,7 @@ package alphabet
import (
"context"
"errors"
"fmt"
"math/big"
"strings"
......@@ -43,7 +44,7 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi
traceIndex = traceIndex.Sub(traceIndex, big.NewInt(1))
// The index cannot be larger than the maximum index as computed by the depth.
if traceIndex.Cmp(big.NewInt(int64(ap.maxLen))) >= 0 {
return nil, nil, nil, ErrIndexTooLarge
return nil, nil, nil, fmt.Errorf("%w traceIndex: %v max: %v pos: %v", ErrIndexTooLarge, traceIndex, ap.maxLen, i)
}
// We extend the deepest hash to the maximum depth if the trace is not expansive.
if traceIndex.Cmp(big.NewInt(int64(len(ap.state)))) >= 0 {
......@@ -54,6 +55,9 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i types.Positi
// Get returns the claim value at the given index in the trace.
func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (common.Hash, error) {
if uint64(i.Depth()) > ap.depth {
return common.Hash{}, fmt.Errorf("%w depth: %v max: %v", ErrIndexTooLarge, i.Depth(), ap.depth)
}
// Step data returns the pre-state, so add 1 to get the state for index i
ti := i.TraceIndex(int(ap.depth))
postPosition := types.NewPosition(int(ap.depth), new(big.Int).Add(ti, big.NewInt(1)))
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -63,6 +63,6 @@ done
# Alphabet game claim construction: keccak256(abi.encode(trace_index, trace[trace_index]))
ROOT_CLAIM=$(cast keccak $(cast abi-encode "f(uint256,uint256)" 15 122))
# Replace the first byte of the claim with the invalid vm status indicator
ROOT_CLAIM="0x01${ROOT_CLAIM:4:60}"
ROOT_CLAIM="0x01${ROOT_CLAIM:4}"
GAME_TYPE=255 ${SOURCE_DIR}/../create_game.sh http://localhost:8545 "${DISPUTE_GAME_FACTORY}" "${ROOT_CLAIM}" --private-key "${DEVNET_SPONSOR}"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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