Commit cfd5f14a authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

Merge pull request #7440 from ethereum-optimism/aj/goerli-between-outputs

op-program: Add compatibility test to verify a section of goerli in CI
parents 55de0b6b 04b6c776
......@@ -1254,6 +1254,22 @@ jobs:
event: fail
template: basic_fail_1
op-program-compat:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
steps:
- checkout
- restore_cache:
name: Restore Go modules cache
key: gomod-{{ checksum "go.sum" }}
- restore_cache:
key: golang-build-cache
- run:
name: compat-goerli
command: |
make run-goerli-verify
working_directory: op-program
check-generated-mocks-op-node:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
......@@ -1432,6 +1448,9 @@ workflows:
- op-stack-go-lint
- devnet-allocs
- l1-geth-version-check
- op-program-compat:
requires:
- op-program-tests
- bedrock-go-tests:
requires:
- go-mod-download
......@@ -1449,6 +1468,7 @@ workflows:
- op-proposer-tests
- op-challenger-tests
- op-program-tests
- op-program-compat
- op-service-tests
- op-e2e-WS-tests
- op-e2e-HTTP-tests
......
......@@ -8,6 +8,8 @@ LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Vers
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-program/version.Meta=$(VERSION_META)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
COMPAT_DIR := temp/compat
op-program: \
op-program-host \
op-program-client \
......@@ -25,13 +27,26 @@ op-program-client-mips:
# result is mips32, big endian, R3000
clean:
rm -rf bin
rm -rf bin "$(COMPAT_DIR)"
test:
go test -v ./...
verify-goerli: op-program-host op-program-client
env GO111MODULE=on go run ./verify/cmd/goerli.go $$L1URL $$L2URL
env GO111MODULE=on go run ./verify/cmd/goerli.go --l1 $$L1URL --l2 $$L2URL
capture-goerli-verify: op-program-host op-program-client
rm -rf "$(COMPAT_DIR)/goerli" "$(COMPAT_DIR)/goerli.tar.bz"
env GO111MODULE=on go run ./verify/cmd/goerli.go --l1 $$L1URL --l2 $$L2URL --datadir "$(COMPAT_DIR)/goerli"
tar jcf "$(COMPAT_DIR)/goerli.tar.bz" -C "$(COMPAT_DIR)" goerli
capture-chain-test-data: capture-goerli-verify
run-goerli-verify: op-program-host op-program-client
mkdir -p "$(COMPAT_DIR)"
curl -L -o "$(COMPAT_DIR)/goerli.tar.bz" https://github.com/ethereum-optimism/chain-test-data/releases/download/2023-10-11/goerli.tar.bz
tar jxf "$(COMPAT_DIR)/goerli.tar.bz" -C "$(COMPAT_DIR)"
./bin/op-program `cat "$(COMPAT_DIR)/goerli/args.txt"`
.PHONY: \
op-program \
......
......@@ -2,10 +2,13 @@ package main
import (
"context"
"flag"
"fmt"
"math/big"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
......@@ -15,28 +18,32 @@ import (
"github.com/ethereum/go-ethereum/rpc"
)
const agreedBlockTrailingDistance = 100
func main() {
if len(os.Args) < 3 {
_, _ = fmt.Fprintln(os.Stderr, "Must specify L1 RPC URL and L2 RPC URL as arguments")
var l1RpcUrl string
var l1RpcKind string
var l2RpcUrl string
var dataDir string
flag.StringVar(&l1RpcUrl, "l1", "", "L1 RPC URL to use")
flag.StringVar(&l1RpcKind, "l1-rpckind", "alchemy", "L1 RPC kind")
flag.StringVar(&l2RpcUrl, "l2", "", "L2 RPC URL to use")
flag.StringVar(&dataDir, "datadir", "",
"Directory to use for storing pre-images. If not set a temporary directory will be used.")
flag.Parse()
if l1RpcUrl == "" || l2RpcUrl == "" {
_, _ = fmt.Fprintln(os.Stderr, "Must specify --l1 and --l2 RPC URLs")
os.Exit(2)
}
l1RpcUrl := os.Args[1]
l2RpcUrl := os.Args[2]
l1RpcKind := "alchemy"
if len(os.Args) > 3 {
l1RpcKind = os.Args[3]
}
goerliOutputAddress := common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0")
err := Run(l1RpcUrl, l1RpcKind, l2RpcUrl, goerliOutputAddress)
err := Run(l1RpcUrl, l1RpcKind, l2RpcUrl, goerliOutputAddress, dataDir)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed: %v\n", err.Error())
os.Exit(1)
}
}
func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common.Address) error {
func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common.Address, dataDir string) error {
ctx := context.Background()
l1RpcClient, err := rpc.Dial(l1RpcUrl)
if err != nil {
......@@ -55,14 +62,7 @@ func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common
return fmt.Errorf("create output oracle bindings: %w", err)
}
// Find L2 finalized head. This is far enough back that we know it's submitted to L1 and won't be re-orged
l2FinalizedHead, err := l2Client.BlockByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
if err != nil {
return fmt.Errorf("get l2 safe head: %w", err)
}
fmt.Printf("Found L2 finalized head number: %v hash: %v\n", l2FinalizedHead.NumberU64(), l2FinalizedHead.Hash())
// Find L1 finalized block. Can't be re-orged and must contain all batches for the L2 finalized block
// Find L1 finalized block. Can't be re-orged.
l1BlockNum := big.NewInt(int64(rpc.FinalizedBlockNumber))
l1HeadBlock, err := l1Client.BlockByNumber(ctx, l1BlockNum)
if err != nil {
......@@ -70,89 +70,68 @@ func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common
}
fmt.Printf("Found l1 head block number: %v hash: %v\n", l1HeadBlock.NumberU64(), l1HeadBlock.Hash())
// Get the most published L2 output from before the finalized block
callOpts := &bind.CallOpts{Context: ctx}
outputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2FinalizedHead.Number())
l1CallOpts := &bind.CallOpts{Context: ctx, BlockNumber: l1BlockNum}
// Find the latest output root published in this finalized block
latestOutputIndex, err := outputOracle.LatestOutputIndex(l1CallOpts)
if err != nil {
fmt.Println("Failed to get output index after finalized block. Checking latest output", "finalized", l2FinalizedHead.Number(), "err", err)
outputIndex, err = outputOracle.LatestOutputIndex(callOpts)
if err != nil {
return fmt.Errorf("get latest output index: %w", err)
}
} else {
outputIndex = outputIndex.Sub(outputIndex, big.NewInt(1))
return fmt.Errorf("fetch latest output index: %w", err)
}
output, err := outputOracle.GetL2Output(callOpts, outputIndex)
output, err := outputOracle.GetL2Output(l1CallOpts, latestOutputIndex)
if err != nil {
return fmt.Errorf("retrieve latest output: %w", err)
}
// Check we wound up with an output prior to the finalized block
if output.L2BlockNumber.Uint64() > l2FinalizedHead.NumberU64() {
return fmt.Errorf("selected output is after finalized head output block: %v, finalized block: %v", output.L2BlockNumber.Uint64(), l2FinalizedHead.NumberU64())
return fmt.Errorf("fetch l2 output %v: %w", latestOutputIndex, err)
}
l1Head := l1HeadBlock.Hash()
l2Claim := common.Hash(output.OutputRoot)
l2BlockNumber := output.L2BlockNumber
// Use an agreed starting L2 block some distance before the block the output claim is from
agreedBlockNumber := uint64(0)
if l2BlockNumber.Uint64() > agreedBlockTrailingDistance {
agreedBlockNumber = l2BlockNumber.Uint64() - agreedBlockTrailingDistance
}
l2AgreedBlock, err := l2Client.BlockByNumber(ctx, big.NewInt(int64(agreedBlockNumber)))
// Use the previous output as the agreed starting point
agreedOutput, err := outputOracle.GetL2Output(l1CallOpts, new(big.Int).Sub(latestOutputIndex, common.Big1))
if err != nil {
return fmt.Errorf("retrieve agreed l2 block: %w", err)
}
agreedOutputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2AgreedBlock.Number())
if err != nil {
return fmt.Errorf("failed to output index after agreed block")
}
// Find an output that differs from what is being claimed
var agreedOutput bindings.TypesOutputProposal
for {
agreedOutput, err = outputOracle.GetL2Output(callOpts, agreedOutputIndex)
if err != nil {
return fmt.Errorf("retrieve agreed output: %w", err)
}
if agreedOutput.OutputRoot != output.OutputRoot {
break
}
fmt.Printf("Output at %v equals output at finalized block. Continuing search...\n", agreedOutput.L2BlockNumber)
agreedOutputIndex.Sub(agreedOutputIndex, big.NewInt(1))
if agreedOutputIndex.Int64() < 0 {
return fmt.Errorf("failed to find an output different from finalized block output")
}
return fmt.Errorf("fetch l2 output before %v: %w", latestOutputIndex, err)
}
l2BlockAtOutput, err := l2Client.BlockByNumber(ctx, agreedOutput.L2BlockNumber)
if err != nil {
return fmt.Errorf("retrieve agreed block: %w", err)
}
l2Head := l2BlockAtOutput.Hash()
l2BlockNumber := output.L2BlockNumber
l2Claim := common.Hash(output.OutputRoot)
l1Head := l1HeadBlock.Hash()
temp, err := os.MkdirTemp("", "oracledata")
if err != nil {
return fmt.Errorf("create temp dir: %w", err)
}
defer func() {
err := os.RemoveAll(temp)
if dataDir == "" {
dataDir, err := os.MkdirTemp("", "oracledata")
if err != nil {
fmt.Println("Failed to remove temp dir:" + err.Error())
return fmt.Errorf("create temp dir: %w", err)
}
defer func() {
err := os.RemoveAll(dataDir)
if err != nil {
fmt.Println("Failed to remove temp dir:" + err.Error())
}
}()
} else {
if err := os.MkdirAll(dataDir, 0755); err != nil {
fmt.Printf("Could not create data directory %v: %v", dataDir, err)
os.Exit(1)
}
}()
fmt.Printf("Using temp dir: %s\n", temp)
}
fmt.Printf("Using dir: %s\n", dataDir)
args := []string{
"--log.level", "DEBUG",
"--network", "goerli",
"--exec", "./bin/op-program-client",
"--datadir", temp,
"--datadir", dataDir,
"--l1.head", l1Head.Hex(),
"--l2.head", l2Head.Hex(),
"--l2.outputroot", common.Bytes2Hex(agreedOutput.OutputRoot[:]),
"--l2.claim", l2Claim.Hex(),
"--l2.blocknumber", l2BlockNumber.String(),
}
fmt.Printf("Configuration: %s\n", args)
argsStr := strings.Join(args, " ")
if err := os.WriteFile(filepath.Join(dataDir, "args.txt"), []byte(argsStr), 0644); err != nil {
fmt.Printf("Could not write args: %v", err)
os.Exit(1)
}
fmt.Printf("Configuration: %s\n", argsStr)
fmt.Println("Running in online mode")
err = runFaultProofProgram(ctx, append(args, "--l1", l1RpcUrl, "--l2", l2RpcUrl, "--l1.rpckind", l1RpcKind))
if err != nil {
......
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