Commit 477591b3 authored by Adrian Sutton's avatar Adrian Sutton

op-program: Setup CI job to periodically verify goerli outputs.

parent 11a204b8
......@@ -3,6 +3,7 @@ version: 2.1
orbs:
go: circleci/go@1.5.0
gcp-cli: circleci/gcp-cli@3.0.1
slack: circleci/slack@4.10.1
commands:
gcp-oidc-authenticate:
description: "Authenticate with GCP using a CircleCI OIDC token."
......@@ -1105,6 +1106,21 @@ jobs:
steps:
- run: echo Done
fpp-verify:
docker:
- image: cimg/go:1.19
steps:
- checkout
- run:
name: verify-goerli
command: |
make verify-goerli
working_directory: op-program
- slack/notify:
channel: C03N11M0BBN
event: fail
template: basic_fail_1
workflows:
main:
jobs:
......@@ -1575,3 +1591,16 @@ workflows:
docker_context: ./ops/docker/ci-builder
context:
- oplabs-gcr
scheduled-fpp:
triggers:
- schedule:
# run every 4 hours
cron: "0 0,6,12,18 * * *"
filters:
branches:
only: [ "develop" ]
jobs:
- fpp-verify:
context:
- slack
- oplabs-fpp-nodes
......@@ -33,6 +33,9 @@ test:
lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is"
verify-goerli: op-program-host op-program-client
env GO111MODULE=on go run ./verify/cmd/goerli.go $$L1URL $$L2URL
.PHONY: \
op-program \
clean \
......
package main
import (
"context"
"fmt"
"math/big"
"os"
"os/exec"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"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")
os.Exit(2)
}
l1RpcUrl := os.Args[1]
l2RpcUrl := os.Args[2]
goerliOutputAddress := common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0")
err := Run(l1RpcUrl, l2RpcUrl, goerliOutputAddress)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed: %v\n", err.Error())
os.Exit(1)
}
}
func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
ctx := context.Background()
l1RpcClient, err := rpc.Dial(l1RpcUrl)
if err != nil {
return fmt.Errorf("dial L1 client: %w", err)
}
l1Client := ethclient.NewClient(l1RpcClient)
l2RpcClient, err := rpc.Dial(l2RpcUrl)
if err != nil {
return fmt.Errorf("dial L2 client: %w", err)
}
l2Client := ethclient.NewClient(l2RpcClient)
outputOracle, err := bindings.NewL2OutputOracle(l2OracleAddr, l1Client)
if err != nil {
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)
}
// Find L1 finalized block. Can't be re-orged and must contain all batches for the L2 finalized block
l1BlockNum := big.NewInt(int64(rpc.FinalizedBlockNumber))
l1HeadBlock, err := l1Client.BlockByNumber(ctx, l1BlockNum)
if err != nil {
return fmt.Errorf("find L1 head: %w", err)
}
// Get the most published L2 output from before the finalized block
callOpts := &bind.CallOpts{Context: ctx}
outputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2FinalizedHead.Number())
if err != nil {
return fmt.Errorf("get output index after finalized block: %w", err)
}
outputIndex = outputIndex.Sub(outputIndex, big.NewInt(1))
output, err := outputOracle.GetL2Output(callOpts, outputIndex)
if err != nil {
return fmt.Errorf("retrieve latest output: %w", 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)))
if err != nil {
return fmt.Errorf("retrieve agreed l2 block: %w", err)
}
l2Head := l2AgreedBlock.Hash()
temp, err := os.MkdirTemp("", "oracledata")
if err != nil {
return fmt.Errorf("create temp dir: %w", err)
}
defer func() {
err := os.RemoveAll(temp)
if err != nil {
println("Failed to remove temp dir:" + err.Error())
}
}()
fmt.Printf("Using temp dir: %s\n", temp)
args := []string{
"--network", "goerli",
"--exec", "./bin/op-program-client",
"--datadir", temp,
"--l1.head", l1Head.Hex(),
"--l2.head", l2Head.Hex(),
"--l2.claim", l2Claim.Hex(),
"--l2.blocknumber", l2BlockNumber.String(),
}
fmt.Printf("Configuration: %s\n", args)
fmt.Println("Running in online mode")
err = runFaultProofProgram(ctx, append(args, "--l1", l1RpcUrl, "--l2", l2RpcUrl))
if err != nil {
return fmt.Errorf("online mode failed: %w", err)
}
fmt.Println("Running in offline mode")
err = runFaultProofProgram(ctx, args)
if err != nil {
return fmt.Errorf("offline mode failed: %w", err)
}
return nil
}
func runFaultProofProgram(ctx context.Context, args []string) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, "./bin/op-program", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
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