Commit 2abb94b5 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into qbzzt/230405-op-proposer

parents 96e1414d bd493973
---
'@eth-optimism/contracts-bedrock': patch
---
Reduce the time that the system dictator deploy scripts wait before checking the chain state.
---
'@eth-optimism/sdk': patch
---
Have SDK automatically create Standard and ETH bridges when L1StandardBridge is provided.
---
'@eth-optimism/batch-submitter-service': patch
---
Allow deposit only batches
---
'@eth-optimism/data-transport-layer': patch
---
Add better logging to DTL about shutoff block
---
'@eth-optimism/contracts-bedrock': minor
---
Bump XDM semver after #5444
---
'@eth-optimism/sdk': patch
---
Fix firefox bug with getTokenPair
---
'@eth-optimism/chain-mon': minor
---
Introduces the balance-mon service to chain-mon.
---
'@eth-optimism/chain-mon': patch
---
Fixes a bug in the wd-mon service where a node connection failure event was not handled correctly
---
'@eth-optimism/contracts-bedrock': patch
---
Makes the Proxy contract inheritable by making functions (public virtual).
---
'@eth-optimism/contracts-bedrock': patch
---
Added a contsructor to the System Dictator
---
'@eth-optimism/hardhat-deploy-config': patch
---
Add getter for other network's deploy config
---
'@eth-optimism/batch-submitter-service': patch
---
fix flag name for MaxStateRootElements in batch-submitter
fix log package for proposer
---
'@eth-optimism/chain-mon': patch
'@eth-optimism/data-transport-layer': patch
'@eth-optimism/fault-detector': patch
'@eth-optimism/message-relayer': patch
'@eth-optimism/replica-healthcheck': patch
---
Empty patch release to re-release packages that failed to be released by a bug in the release process.
This diff is collapsed.
...@@ -27,7 +27,6 @@ jobs: ...@@ -27,7 +27,6 @@ jobs:
op-exporter: ${{ steps.packages.outputs.op-exporter }} op-exporter: ${{ steps.packages.outputs.op-exporter }}
l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }} l2geth-exporter: ${{ steps.packages.outputs.l2geth-exporter }}
batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }} batch-submitter-service: ${{ steps.packages.outputs.batch-submitter-service }}
ci-builder: ${{ steps.packages.outputs.ci-builder }}
foundry: ${{ steps.packages.outputs.foundry }} foundry: ${{ steps.packages.outputs.foundry }}
endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }} endpoint-monitor: ${{ steps.packages.outputs.endpoint-monitor }}
...@@ -159,32 +158,6 @@ jobs: ...@@ -159,32 +158,6 @@ jobs:
push: true push: true
tags: ethereumoptimism/hardhat-node:${{ needs.release.outputs.hardhat-node }},ethereumoptimism/hardhat-node:latest tags: ethereumoptimism/hardhat-node:${{ needs.release.outputs.hardhat-node }},ethereumoptimism/hardhat-node:latest
ci-builder:
name: Publish ci-builder ${{ needs.release.outputs.ci-builder }}
needs: release
if: needs.release.outputs.ci-builder != ''
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_USERNAME }}
password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN_SECRET }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Publish ci-builder
uses: docker/build-push-action@v2
with:
context: ./ops/docker/ci-builder
file: ./ops/docker/ci-builder/Dockerfile
push: true
tags: ethereumoptimism/ci-builder:${{ needs.release.outputs.ci-builder }},ethereumoptimism/ci-builder:latest
foundry: foundry:
name: Publish foundry ${{ needs.release.outputs.foundry }} name: Publish foundry ${{ needs.release.outputs.foundry }}
needs: release needs: release
......
# @eth-optimism/batch-submitter-service # @eth-optimism/batch-submitter-service
## 0.1.16
### Patch Changes
- 32bd79ec9: Allow deposit only batches
- da79ef441: fix flag name for MaxStateRootElements in batch-submitter
fix log package for proposer
## 0.1.15 ## 0.1.15
### Patch Changes ### Patch Changes
......
{ {
"name": "@eth-optimism/batch-submitter-service", "name": "@eth-optimism/batch-submitter-service",
"version": "0.1.15", "version": "0.1.16",
"private": true, "private": true,
"devDependencies": {} "devDependencies": {}
} }
...@@ -221,7 +221,7 @@ Once you’ve configured your network, it’s time to deploy the L1 smart contra ...@@ -221,7 +221,7 @@ Once you’ve configured your network, it’s time to deploy the L1 smart contra
1. Once you’re ready, deploy the L1 smart contracts: 1. Once you’re ready, deploy the L1 smart contracts:
```bash ```bash
npx hardhat deploy --network getting-started npx hardhat deploy --network getting-started --tags l1
``` ```
Contract deployment can take up to 15 minutes. Please wait for all smart contracts to be fully deployed before continuing to the next step. Contract deployment can take up to 15 minutes. Please wait for all smart contracts to be fully deployed before continuing to the next step.
......
...@@ -30,10 +30,10 @@ ...@@ -30,10 +30,10 @@
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.5.4", "@babel/eslint-parser": "^7.5.4",
"@eth-optimism/contracts": "^0.5.40", "@eth-optimism/contracts": "^0.5.40",
"@eth-optimism/contracts-bedrock": "0.13.1", "@eth-optimism/contracts-bedrock": "0.13.2",
"@eth-optimism/contracts-periphery": "^1.0.7", "@eth-optimism/contracts-periphery": "^1.0.7",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.0.1", "@eth-optimism/sdk": "2.0.2",
"@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/providers": "^5.7.0", "@ethersproject/providers": "^5.7.0",
"@ethersproject/transactions": "^5.7.0", "@ethersproject/transactions": "^5.7.0",
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
gethrpc "github.com/ethereum/go-ethereum/rpc" gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-batcher/rpc" "github.com/ethereum-optimism/optimism/op-batcher/rpc"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
...@@ -30,6 +31,9 @@ const ( ...@@ -30,6 +31,9 @@ const (
// of a closure allows the parameters bound to the top-level main package, e.g. // of a closure allows the parameters bound to the top-level main package, e.g.
// GitVersion, to be captured and used once the function is executed. // GitVersion, to be captured and used once the function is executed.
func Main(version string, cliCtx *cli.Context) error { func Main(version string, cliCtx *cli.Context) error {
if err := flags.CheckRequired(cliCtx); err != nil {
return err
}
cfg := NewConfig(cliCtx) cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err) return fmt.Errorf("invalid CLI flags: %w", err)
......
...@@ -137,7 +137,7 @@ func newMiniL2BlockWithNumberParent(numTx int, number *big.Int, parent common.Ha ...@@ -137,7 +137,7 @@ func newMiniL2BlockWithNumberParent(numTx int, number *big.Int, parent common.Ha
Difficulty: common.Big0, Difficulty: common.Big0,
Number: big.NewInt(100), Number: big.NewInt(100),
}, nil, nil, nil, trie.NewStackTrie(nil)) }, nil, nil, nil, trie.NewStackTrie(nil))
l1InfoTx, err := derive.L1InfoDeposit(0, l1Block, eth.SystemConfig{}, false) l1InfoTx, err := derive.L1InfoDeposit(0, eth.BlockToInfo(l1Block), eth.SystemConfig{}, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
...@@ -517,7 +517,7 @@ func TestChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T) { ...@@ -517,7 +517,7 @@ func TestChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T) {
Difficulty: common.Big0, Difficulty: common.Big0,
Number: common.Big0, Number: common.Big0,
}, nil, nil, nil, trie.NewStackTrie(nil)) }, nil, nil, nil, trie.NewStackTrie(nil))
l1InfoTx, _ := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) l1InfoTx, _ := derive.L1InfoDeposit(0, eth.BlockToInfo(lBlock), eth.SystemConfig{}, false)
txs := []*types.Transaction{types.NewTx(l1InfoTx)} txs := []*types.Transaction{types.NewTx(l1InfoTx)}
a := types.NewBlock(&types.Header{ a := types.NewBlock(&types.Header{
Number: big.NewInt(0), Number: big.NewInt(0),
......
...@@ -82,7 +82,7 @@ func (s *channelManager) TxFailed(id txID) { ...@@ -82,7 +82,7 @@ func (s *channelManager) TxFailed(id txID) {
} }
s.metr.RecordBatchTxFailed() s.metr.RecordBatchTxFailed()
if s.closed && len(s.confirmedTransactions) == 0 && len(s.pendingTransactions) == 0 { if s.closed && len(s.confirmedTransactions) == 0 && len(s.pendingTransactions) == 0 && s.pendingChannel != nil {
s.log.Info("Channel has no submitted transactions, clearing for shutdown", "chID", s.pendingChannel.ID()) s.log.Info("Channel has no submitted transactions, clearing for shutdown", "chID", s.pendingChannel.ID())
s.clearPendingChannel() s.clearPendingChannel()
} }
......
...@@ -191,7 +191,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) { ...@@ -191,7 +191,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) {
if err != nil { if err != nil {
l.log.Warn("Error calculating L2 block range", "err", err) l.log.Warn("Error calculating L2 block range", "err", err)
return return
} else if start.Number == end.Number { } else if start.Number >= end.Number {
return return
} }
......
package doc
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/olekukonko/tablewriter"
"github.com/urfave/cli"
)
var Subcommands = cli.Commands{
{
Name: "metrics",
Usage: "Dumps a list of supported metrics to stdout",
Flags: []cli.Flag{
cli.StringFlag{
Name: "format",
Value: "markdown",
Usage: "Output format (json|markdown)",
},
},
Action: func(ctx *cli.Context) error {
m := metrics.NewMetrics("default")
supportedMetrics := m.Document()
format := ctx.String("format")
if format != "markdown" && format != "json" {
return fmt.Errorf("invalid format: %s", format)
}
if format == "json" {
enc := json.NewEncoder(os.Stdout)
return enc.Encode(supportedMetrics)
}
table := tablewriter.NewWriter(os.Stdout)
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
table.SetCenterSeparator("|")
table.SetAutoWrapText(false)
table.SetHeader([]string{"Metric", "Description", "Labels", "Type"})
var data [][]string
for _, metric := range supportedMetrics {
labels := strings.Join(metric.Labels, ",")
data = append(data, []string{metric.Name, metric.Help, labels, metric.Type})
}
table.AppendBulk(data)
table.Render()
return nil
},
},
}
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-batcher/cmd/doc"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -26,10 +27,15 @@ func main() { ...@@ -26,10 +27,15 @@ func main() {
app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate) app.Version = fmt.Sprintf("%s-%s-%s", Version, GitCommit, GitDate)
app.Name = "op-batcher" app.Name = "op-batcher"
app.Usage = "Batch Submitter Service" app.Usage = "Batch Submitter Service"
app.Description = "Service for generating and submitting L2 tx batches " + app.Description = "Service for generating and submitting L2 tx batches to L1"
"to L1"
app.Action = curryMain(Version) app.Action = curryMain(Version)
app.Commands = []cli.Command{
{
Name: "doc",
Subcommands: doc.Subcommands,
},
}
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
......
package flags package flags
import ( import (
"fmt"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/rpc" "github.com/ethereum-optimism/optimism/op-batcher/rpc"
...@@ -17,37 +19,32 @@ const envVarPrefix = "OP_BATCHER" ...@@ -17,37 +19,32 @@ const envVarPrefix = "OP_BATCHER"
var ( var (
// Required flags // Required flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
Required: true, EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"),
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"),
} }
L2EthRpcFlag = cli.StringFlag{ L2EthRpcFlag = cli.StringFlag{
Name: "l2-eth-rpc", Name: "l2-eth-rpc",
Usage: "HTTP provider URL for L2 execution engine", Usage: "HTTP provider URL for L2 execution engine",
Required: true, EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2_ETH_RPC"),
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2_ETH_RPC"),
} }
RollupRpcFlag = cli.StringFlag{ RollupRpcFlag = cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for Rollup node", Usage: "HTTP provider URL for Rollup node",
Required: true, EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"),
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"),
} }
SubSafetyMarginFlag = cli.Uint64Flag{ SubSafetyMarginFlag = cli.Uint64Flag{
Name: "sub-safety-margin", Name: "sub-safety-margin",
Usage: "The batcher tx submission safety margin (in #L1-blocks) to subtract " + Usage: "The batcher tx submission safety margin (in #L1-blocks) to subtract " +
"from a channel's timeout and sequencing window, to guarantee safe inclusion " + "from a channel's timeout and sequencing window, to guarantee safe inclusion " +
"of a channel on L1.", "of a channel on L1.",
Required: true, EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SUB_SAFETY_MARGIN"),
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SUB_SAFETY_MARGIN"),
} }
PollIntervalFlag = cli.DurationFlag{ PollIntervalFlag = cli.DurationFlag{
Name: "poll-interval", Name: "poll-interval",
Usage: "Delay between querying L2 for more transactions and " + Usage: "Delay between querying L2 for more transactions and " +
"creating a new batch", "creating a new batch",
Required: true, EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"),
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"),
} }
// Optional flags // Optional flags
...@@ -108,8 +105,7 @@ var optionalFlags = []cli.Flag{ ...@@ -108,8 +105,7 @@ var optionalFlags = []cli.Flag{
} }
func init() { func init() {
requiredFlags = append(requiredFlags, oprpc.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oprpc.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
...@@ -121,3 +117,12 @@ func init() { ...@@ -121,3 +117,12 @@ func init() {
// Flags contains the list of configuration options available to the binary. // Flags contains the list of configuration options available to the binary.
var Flags []cli.Flag var Flags []cli.Flag
func CheckRequired(ctx *cli.Context) error {
for _, f := range requiredFlags {
if !ctx.GlobalIsSet(f.GetName()) {
return fmt.Errorf("flag %s is required", f.GetName())
}
}
return nil
}
...@@ -58,12 +58,14 @@ type Metrics struct { ...@@ -58,12 +58,14 @@ type Metrics struct {
PendingBlocksCount prometheus.GaugeVec PendingBlocksCount prometheus.GaugeVec
BlocksAddedCount prometheus.Gauge BlocksAddedCount prometheus.Gauge
ChannelInputBytes prometheus.GaugeVec ChannelInputBytes prometheus.GaugeVec
ChannelReadyBytes prometheus.Gauge ChannelReadyBytes prometheus.Gauge
ChannelOutputBytes prometheus.Gauge ChannelOutputBytes prometheus.Gauge
ChannelClosedReason prometheus.Gauge ChannelClosedReason prometheus.Gauge
ChannelNumFrames prometheus.Gauge ChannelNumFrames prometheus.Gauge
ChannelComprRatio prometheus.Histogram ChannelComprRatio prometheus.Histogram
ChannelInputBytesTotal prometheus.Counter
ChannelOutputBytesTotal prometheus.Counter
BatcherTxEvs opmetrics.EventVec BatcherTxEvs opmetrics.EventVec
} }
...@@ -100,7 +102,7 @@ func NewMetrics(procName string) *Metrics { ...@@ -100,7 +102,7 @@ func NewMetrics(procName string) *Metrics {
Help: "1 if the op-batcher has finished starting up", Help: "1 if the op-batcher has finished starting up",
}), }),
ChannelEvs: opmetrics.NewEventVec(factory, ns, "channel", "Channel", []string{"stage"}), ChannelEvs: opmetrics.NewEventVec(factory, ns, "", "channel", "Channel", []string{"stage"}),
PendingBlocksCount: *factory.NewGaugeVec(prometheus.GaugeOpts{ PendingBlocksCount: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns, Namespace: ns,
...@@ -144,8 +146,18 @@ func NewMetrics(procName string) *Metrics { ...@@ -144,8 +146,18 @@ func NewMetrics(procName string) *Metrics {
Help: "Compression ratios of closed channel.", Help: "Compression ratios of closed channel.",
Buckets: append([]float64{0.1, 0.2}, prometheus.LinearBuckets(0.3, 0.05, 14)...), Buckets: append([]float64{0.1, 0.2}, prometheus.LinearBuckets(0.3, 0.05, 14)...),
}), }),
ChannelInputBytesTotal: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns,
Name: "input_bytes_total",
Help: "Total number of bytes to a channel.",
}),
ChannelOutputBytesTotal: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns,
Name: "output_bytes_total",
Help: "Total number of compressed output bytes from a channel.",
}),
BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "batcher_tx", "BatcherTx", []string{"stage"}), BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "", "batcher_tx", "BatcherTx", []string{"stage"}),
} }
} }
...@@ -219,6 +231,8 @@ func (m *Metrics) RecordChannelClosed(id derive.ChannelID, numPendingBlocks int, ...@@ -219,6 +231,8 @@ func (m *Metrics) RecordChannelClosed(id derive.ChannelID, numPendingBlocks int,
m.ChannelNumFrames.Set(float64(numFrames)) m.ChannelNumFrames.Set(float64(numFrames))
m.ChannelInputBytes.WithLabelValues(StageClosed).Set(float64(inputBytes)) m.ChannelInputBytes.WithLabelValues(StageClosed).Set(float64(inputBytes))
m.ChannelOutputBytes.Set(float64(outputComprBytes)) m.ChannelOutputBytes.Set(float64(outputComprBytes))
m.ChannelInputBytesTotal.Add(float64(inputBytes))
m.ChannelOutputBytesTotal.Add(float64(outputComprBytes))
var comprRatio float64 var comprRatio float64
if inputBytes > 0 { if inputBytes > 0 {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -3,6 +3,7 @@ package main ...@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
...@@ -168,10 +169,14 @@ func main() { ...@@ -168,10 +169,14 @@ func main() {
var block *types.Block var block *types.Block
tag := config.L1StartingBlockTag tag := config.L1StartingBlockTag
if tag.BlockNumber != nil { if tag == nil {
block, err = l1Client.BlockByNumber(context.Background(), big.NewInt(tag.BlockNumber.Int64())) return errors.New("l1StartingBlockTag cannot be nil")
} else if tag.BlockHash != nil { }
block, err = l1Client.BlockByHash(context.Background(), *tag.BlockHash) log.Info("Using L1 Starting Block Tag", "tag", tag.String())
if number, isNumber := tag.Number(); isNumber {
block, err = l1Client.BlockByNumber(context.Background(), big.NewInt(number.Int64()))
} else if hash, isHash := tag.Hash(); isHash {
block, err = l1Client.BlockByHash(context.Background(), hash)
} else { } else {
return fmt.Errorf("invalid l1StartingBlockTag in deploy config: %v", tag) return fmt.Errorf("invalid l1StartingBlockTag in deploy config: %v", tag)
} }
......
...@@ -22,11 +22,14 @@ import ( ...@@ -22,11 +22,14 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
) )
// abiTrue represents the storage representation of the boolean // abiTrue represents the storage representation of the boolean
...@@ -103,6 +106,10 @@ func main() { ...@@ -103,6 +106,10 @@ func main() {
Name: "evm-messages", Name: "evm-messages",
Usage: "Path to evm-messages.json", Usage: "Path to evm-messages.json",
}, },
&cli.StringFlag{
Name: "witness-file",
Usage: "Path to l2geth witness file",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "private-key", Name: "private-key",
Usage: "Key to sign transactions with", Usage: "Key to sign transactions with",
...@@ -112,6 +119,10 @@ func main() { ...@@ -112,6 +119,10 @@ func main() {
Value: "bad-withdrawals.json", Value: "bad-withdrawals.json",
Usage: "Path to write JSON file of bad withdrawals to manually inspect", Usage: "Path to write JSON file of bad withdrawals to manually inspect",
}, },
&cli.StringFlag{
Name: "storage-out",
Usage: "Path to write text file of L2ToL1MessagePasser storage",
},
}, },
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
clients, err := util.NewClients(ctx) clients, err := util.NewClients(ctx)
...@@ -159,10 +170,11 @@ func main() { ...@@ -159,10 +170,11 @@ func main() {
} }
outfile := ctx.String("bad-withdrawals-out") outfile := ctx.String("bad-withdrawals-out")
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o755) f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
if err != nil { if err != nil {
return err return err
} }
defer f.Close()
// create a transactor // create a transactor
opts, err := newTransactor(ctx) opts, err := newTransactor(ctx)
...@@ -173,6 +185,28 @@ func main() { ...@@ -173,6 +185,28 @@ func main() {
// Need this to compare in event parsing // Need this to compare in event parsing
l1StandardBridgeAddress := common.HexToAddress(ctx.String("l1-standard-bridge-address")) l1StandardBridgeAddress := common.HexToAddress(ctx.String("l1-standard-bridge-address"))
if storageOutfile := ctx.String("storage-out"); storageOutfile != "" {
ff, err := os.OpenFile(storageOutfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
if err != nil {
return err
}
defer ff.Close()
log.Info("Fetching storage for L2ToL1MessagePasser")
if storageRange, err := callStorageRange(clients, predeploys.L2ToL1MessagePasserAddr); err != nil {
log.Info("error getting storage range", "err", err)
} else {
str := ""
for key, value := range storageRange {
str += fmt.Sprintf("%s: %s\n", key.Hex(), value.Hex())
}
_, err = ff.WriteString(str)
if err != nil {
return err
}
}
}
// iterate over all of the withdrawals and submit them // iterate over all of the withdrawals and submit them
for i, wd := range wds { for i, wd := range wds {
log.Info("Processing withdrawal", "index", i) log.Info("Processing withdrawal", "index", i)
...@@ -230,7 +264,7 @@ func main() { ...@@ -230,7 +264,7 @@ func main() {
// successful messages can be skipped, received messages failed // successful messages can be skipped, received messages failed
// their execution and should be replayed // their execution and should be replayed
if isSuccessNew { if isSuccessNew {
log.Info("Message already relayed", "index", i, "hash", hash, "slot", slot) log.Info("Message already relayed", "index", i, "hash", hash.Hex(), "slot", slot.Hex())
continue continue
} }
...@@ -244,7 +278,7 @@ func main() { ...@@ -244,7 +278,7 @@ func main() {
// the value should be set to a boolean in storage // the value should be set to a boolean in storage
if !bytes.Equal(storageValue, abiTrue.Bytes()) { if !bytes.Equal(storageValue, abiTrue.Bytes()) {
return fmt.Errorf("storage slot %x not found in state", slot) return fmt.Errorf("storage slot %x not found in state", slot.Hex())
} }
legacySlot, err := wd.StorageSlot() legacySlot, err := wd.StorageSlot()
...@@ -439,10 +473,48 @@ func callTrace(c *util.Clients, receipt *types.Receipt) (callFrame, error) { ...@@ -439,10 +473,48 @@ func callTrace(c *util.Clients, receipt *types.Receipt) (callFrame, error) {
Tracer: &tracer, Tracer: &tracer,
} }
err := c.L1RpcClient.Call(&finalizationTrace, "debug_traceTransaction", receipt.TxHash, traceConfig) err := c.L1RpcClient.Call(&finalizationTrace, "debug_traceTransaction", receipt.TxHash, traceConfig)
return finalizationTrace, err
}
func callStorageRangeAt(
client *rpc.Client,
blockHash common.Hash,
txIndex int,
addr common.Address,
keyStart hexutil.Bytes,
maxResult int,
) (*eth.StorageRangeResult, error) {
var storageRange *eth.StorageRangeResult
err := client.Call(&storageRange, "debug_storageRangeAt", blockHash, txIndex, addr, keyStart, maxResult)
return storageRange, err
}
func callStorageRange(c *util.Clients, addr common.Address) (state.Storage, error) {
header, err := c.L2Client.HeaderByNumber(context.Background(), nil)
if err != nil { if err != nil {
return finalizationTrace, err return nil, err
} }
return finalizationTrace, err hash := header.Hash()
keyStart := hexutil.Bytes(common.Hash{}.Bytes())
maxResult := 1000
ret := make(state.Storage)
for {
result, err := callStorageRangeAt(c.L2RpcClient, hash, 0, addr, keyStart, maxResult)
if err != nil {
return nil, err
}
for key, value := range result.Storage {
ret[key] = value.Value
}
if result.NextKey == nil {
break
} else {
keyStart = hexutil.Bytes(result.NextKey.Bytes())
}
}
return ret, nil
} }
// handleFinalizeETHWithdrawal will ensure that the calldata is correct // handleFinalizeETHWithdrawal will ensure that the calldata is correct
...@@ -702,11 +774,16 @@ func newContracts(ctx *cli.Context, l1Backend, l2Backend bind.ContractBackend) ( ...@@ -702,11 +774,16 @@ func newContracts(ctx *cli.Context, l1Backend, l2Backend bind.ContractBackend) (
func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.LegacyWithdrawal, error) { func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.LegacyWithdrawal, error) {
ovmMsgs := ctx.String("ovm-messages") ovmMsgs := ctx.String("ovm-messages")
evmMsgs := ctx.String("evm-messages") evmMsgs := ctx.String("evm-messages")
witnessFile := ctx.String("witness-file")
log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs) log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs, "witness-file", witnessFile)
ovmMessages, err := crossdomain.NewSentMessageFromJSON(ovmMsgs) var ovmMessages []*crossdomain.SentMessage
if err != nil { var err error
return nil, err if ovmMsgs != "" {
ovmMessages, err = crossdomain.NewSentMessageFromJSON(ovmMsgs)
if err != nil {
return nil, err
}
} }
// use empty ovmMessages if its not mainnet. The mainnet messages are // use empty ovmMessages if its not mainnet. The mainnet messages are
...@@ -716,9 +793,19 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy ...@@ -716,9 +793,19 @@ func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.Legacy
ovmMessages = []*crossdomain.SentMessage{} ovmMessages = []*crossdomain.SentMessage{}
} }
evmMessages, err := crossdomain.NewSentMessageFromJSON(evmMsgs) var evmMessages []*crossdomain.SentMessage
if err != nil { if witnessFile != "" {
return nil, err evmMessages, _, err = crossdomain.ReadWitnessData(witnessFile)
if err != nil {
return nil, err
}
} else if evmMsgs != "" {
evmMessages, err = crossdomain.NewSentMessageFromJSON(evmMsgs)
if err != nil {
return nil, err
}
} else {
return nil, errors.New("must provide either witness file or evm messages")
} }
migrationData := crossdomain.MigrationData{ migrationData := crossdomain.MigrationData{
......
...@@ -96,17 +96,12 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com ...@@ -96,17 +96,12 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
return w, nil return w, nil
} }
// MigrateWithdrawalGasLimit computes the gas limit for the migrated withdrawal.
func MigrateWithdrawalGasLimit(data []byte) uint64 { func MigrateWithdrawalGasLimit(data []byte) uint64 {
// Compute the cost of the calldata // Compute the upper bound on the gas limit. This could be more
dataCost := uint64(0) // accurate if individual 0 bytes and non zero bytes were accounted
for _, b := range data { // for.
if b == 0 { dataCost := uint64(len(data)) * params.TxDataNonZeroGasEIP2028
dataCost += params.TxDataZeroGas
} else {
dataCost += params.TxDataNonZeroGasEIP2028
}
}
// Set the outer gas limit. This cannot be zero // Set the outer gas limit. This cannot be zero
gasLimit := dataCost + 200_000 gasLimit := dataCost + 200_000
// Cap the gas limit to be 25 million to prevent creating withdrawals // Cap the gas limit to be 25 million to prevent creating withdrawals
......
...@@ -71,15 +71,15 @@ func TestMigrateWithdrawalGasLimit(t *testing.T) { ...@@ -71,15 +71,15 @@ func TestMigrateWithdrawalGasLimit(t *testing.T) {
}, },
{ {
input: []byte{0xff, 0x00}, input: []byte{0xff, 0x00},
output: 200_000 + 16 + 4, output: 200_000 + 16 + 16,
}, },
{ {
input: []byte{0x00}, input: []byte{0x00},
output: 200_000 + 4, output: 200_000 + 16,
}, },
{ {
input: []byte{0x00, 0x00, 0x00}, input: []byte{0x00, 0x00, 0x00},
output: 200_000 + 4 + 4 + 4, output: 200_000 + 16 + 16 + 16,
}, },
} }
......
...@@ -41,6 +41,40 @@ func NewSentMessageFromJSON(path string) ([]*SentMessage, error) { ...@@ -41,6 +41,40 @@ func NewSentMessageFromJSON(path string) ([]*SentMessage, error) {
return j, nil return j, nil
} }
// decodeWitnessCalldata abi decodes the calldata encoded in the input witness
// file. It errors if the 4 byte selector is not specifically for `passMessageToL1`.
// It also errors if the abi decoding fails.
func decodeWitnessCalldata(msg []byte) ([]byte, error) {
abi, err := bindings.LegacyMessagePasserMetaData.GetAbi()
if err != nil {
panic("should always be able to get message passer abi")
}
if size := len(msg); size < 4 {
return nil, fmt.Errorf("message too short: %d", size)
}
method, err := abi.MethodById(msg[:4])
if err != nil {
return nil, err
}
if method.Sig != "passMessageToL1(bytes)" {
return nil, fmt.Errorf("unknown method: %s", method.Name)
}
out, err := method.Inputs.Unpack(msg[4:])
if err != nil {
return nil, err
}
cast, ok := out[0].([]byte)
if !ok {
panic("should always be able to cast type []byte")
}
return cast, nil
}
// ReadWitnessData will read messages and addresses from a raw l2geth state // ReadWitnessData will read messages and addresses from a raw l2geth state
// dump file. // dump file.
func ReadWitnessData(path string) ([]*SentMessage, OVMETHAddresses, error) { func ReadWitnessData(path string) ([]*SentMessage, OVMETHAddresses, error) {
...@@ -72,30 +106,18 @@ func ReadWitnessData(path string) ([]*SentMessage, OVMETHAddresses, error) { ...@@ -72,30 +106,18 @@ func ReadWitnessData(path string) ([]*SentMessage, OVMETHAddresses, error) {
msg = "0x" + msg msg = "0x" + msg
} }
abi, err := bindings.LegacyMessagePasserMetaData.GetAbi()
if err != nil {
return nil, nil, fmt.Errorf("failed to get abi: %w", err)
}
msgB := hexutil.MustDecode(msg) msgB := hexutil.MustDecode(msg)
method, err := abi.MethodById(msgB[:4])
if err != nil {
return nil, nil, fmt.Errorf("failed to get method: %w", err)
}
out, err := method.Inputs.Unpack(msgB[4:]) // Skip any errors
calldata, err := decodeWitnessCalldata(msgB)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to unpack: %w", err) log.Warn("cannot decode witness calldata", "err", err)
} continue
cast, ok := out[0].([]byte)
if !ok {
return nil, nil, fmt.Errorf("failed to cast to bytes")
} }
witnesses = append(witnesses, &SentMessage{ witnesses = append(witnesses, &SentMessage{
Who: common.HexToAddress(splits[1]), Who: common.HexToAddress(splits[1]),
Msg: cast, Msg: calldata,
}) })
case "ETH": case "ETH":
addresses[common.HexToAddress(splits[1])] = true addresses[common.HexToAddress(splits[1])] = true
......
package crossdomain package crossdomain
import ( import (
"context"
"math/big"
"testing" "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/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -41,3 +51,163 @@ func TestRead(t *testing.T) { ...@@ -41,3 +51,163 @@ func TestRead(t *testing.T) {
common.HexToAddress("0x6340d44c5174588B312F545eEC4a42f8a514eF50"): true, common.HexToAddress("0x6340d44c5174588B312F545eEC4a42f8a514eF50"): true,
}, addresses) }, addresses)
} }
// TestDecodeWitnessCallData tests that the witness data is parsed correctly
// from an input bytes slice.
func TestDecodeWitnessCallData(t *testing.T) {
tests := []struct {
name string
err bool
msg []byte
want []byte
}{
{
name: "too-small",
err: true,
msg: common.FromHex("0x0000"),
},
{
name: "unknown-selector",
err: true,
msg: common.FromHex("0x00000000"),
},
{
name: "wrong-selector",
err: true,
// 0x54fd4d50 is the selector for `version()`
msg: common.FromHex("0x54fd4d50"),
},
{
name: "invalid-calldata-only-selector",
err: true,
// 0xcafa81dc is the selector for `passMessageToL1(bytes)`
msg: common.FromHex("0xcafa81dc"),
},
{
name: "invalid-calldata-invalid-bytes",
err: true,
// 0xcafa81dc is the selector for passMessageToL1(bytes)
msg: common.FromHex("0xcafa81dc0000"),
},
{
name: "valid-calldata",
msg: common.FromHex(
"0xcafa81dc" +
"0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000002" +
"1234000000000000000000000000000000000000000000000000000000000000",
),
want: common.FromHex("0x1234"),
},
}
for _, tt := range tests {
test := tt
t.Run(test.name, func(t *testing.T) {
if test.err {
_, err := decodeWitnessCalldata(test.msg)
require.Error(t, err)
} else {
want, err := decodeWitnessCalldata(test.msg)
require.NoError(t, err)
require.Equal(t, test.want, want)
}
})
}
}
// TestMessagePasserSafety ensures that the LegacyMessagePasser contract reverts when it is called
// with incorrect calldata. The function signature is correct but the calldata is not abi encoded
// correctly. It is expected the solidity reverts when it cannot abi decode the calldata correctly.
// Only a call to `passMessageToL1` with abi encoded `bytes` will result in the `successfulMessages`
// mapping being updated.
func TestMessagePasserSafety(t *testing.T) {
testKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
opts, err := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337))
require.NoError(t, err)
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{testAddr: {Balance: big.NewInt(10000000000000000)}},
30_000_000,
)
defer backend.Close()
// deploy the LegacyMessagePasser contract
addr, tx, contract, err := bindings.DeployLegacyMessagePasser(opts, backend)
require.NoError(t, err)
backend.Commit()
_, err = bind.WaitMined(context.Background(), backend, tx)
require.NoError(t, err)
// ensure that it deployed
code, err := backend.CodeAt(context.Background(), addr, nil)
require.NoError(t, err)
require.True(t, len(code) > 0)
// dummy message
msg := []byte{0x00, 0x01, 0x02, 0x03}
// call `passMessageToL1`
msgTx, err := contract.PassMessageToL1(opts, msg)
require.NoError(t, err)
// ensure that the receipt is successful
backend.Commit()
msgReceipt, err := bind.WaitMined(context.Background(), backend, msgTx)
require.NoError(t, err)
require.Equal(t, msgReceipt.Status, types.ReceiptStatusSuccessful)
// check for the data in the `successfulMessages` mapping
data := make([]byte, len(msg)+len(testAddr))
copy(data[:], msg)
copy(data[len(msg):], testAddr.Bytes())
digest := crypto.Keccak256Hash(data)
contains, err := contract.SentMessages(&bind.CallOpts{}, digest)
require.NoError(t, err)
require.True(t, contains)
// build a transaction with improperly formatted calldata
nonce, err := backend.NonceAt(context.Background(), testAddr, nil)
require.NoError(t, err)
// append msg without abi encoding it
selector := crypto.Keccak256([]byte("passMessageToL1(bytes)"))[0:4]
require.Equal(t, selector, hexutil.MustDecode("0xcafa81dc"))
calldata := append(selector, msg...)
faultyTransaction, err := opts.Signer(testAddr, types.NewTx(&types.DynamicFeeTx{
ChainID: big.NewInt(1337),
Nonce: nonce,
GasTipCap: msgTx.GasTipCap(),
GasFeeCap: msgTx.GasFeeCap(),
Gas: msgTx.Gas() * 2,
To: msgTx.To(),
Data: calldata,
}))
require.NoError(t, err)
err = backend.SendTransaction(context.Background(), faultyTransaction)
require.NoError(t, err)
// the transaction should revert
backend.Commit()
badReceipt, err := bind.WaitMined(context.Background(), backend, faultyTransaction)
require.NoError(t, err)
require.Equal(t, badReceipt.Status, types.ReceiptStatusFailed)
// test the transaction calldata against the abi unpacking
abi, err := bindings.LegacyMessagePasserMetaData.GetAbi()
require.NoError(t, err)
method, err := abi.MethodById(selector)
require.NoError(t, err)
require.Equal(t, method.Name, "passMessageToL1")
// the faulty transaction has the correct 4 byte selector but doesn't
// have abi encoded bytes following it
require.Equal(t, faultyTransaction.Data()[:4], selector)
_, err = method.Inputs.Unpack(faultyTransaction.Data()[4:])
require.Error(t, err)
// the original transaction has the correct 4 byte selector and abi encoded bytes
_, err = method.Inputs.Unpack(msgTx.Data()[4:])
require.NoError(t, err)
}
...@@ -208,6 +208,11 @@ func (d *DeployConfig) Check() error { ...@@ -208,6 +208,11 @@ func (d *DeployConfig) Check() error {
if d.L2GenesisBlockGasLimit == 0 { if d.L2GenesisBlockGasLimit == 0 {
return fmt.Errorf("%w: L2 genesis block gas limit cannot be 0", ErrInvalidDeployConfig) return fmt.Errorf("%w: L2 genesis block gas limit cannot be 0", ErrInvalidDeployConfig)
} }
// When the initial resource config is made to be configurable by the DeployConfig, ensure
// that this check is updated to use the values from the DeployConfig instead of the defaults.
if uint64(d.L2GenesisBlockGasLimit) < uint64(defaultResourceConfig.MaxResourceLimit+defaultResourceConfig.SystemTxMaxGas) {
return fmt.Errorf("%w: L2 genesis block gas limit is too small", ErrInvalidDeployConfig)
}
if d.L2GenesisBlockBaseFeePerGas == nil { if d.L2GenesisBlockBaseFeePerGas == nil {
return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig) return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig)
} }
...@@ -493,3 +498,18 @@ func (m *MarshalableRPCBlockNumberOrHash) UnmarshalJSON(b []byte) error { ...@@ -493,3 +498,18 @@ func (m *MarshalableRPCBlockNumberOrHash) UnmarshalJSON(b []byte) error {
*m = asMarshalable *m = asMarshalable
return nil return nil
} }
// Number wraps the rpc.BlockNumberOrHash Number method.
func (m *MarshalableRPCBlockNumberOrHash) Number() (rpc.BlockNumber, bool) {
return (*rpc.BlockNumberOrHash)(m).Number()
}
// Hash wraps the rpc.BlockNumberOrHash Hash method.
func (m *MarshalableRPCBlockNumberOrHash) Hash() (common.Hash, bool) {
return (*rpc.BlockNumberOrHash)(m).Hash()
}
// String wraps the rpc.BlockNumberOrHash String method.
func (m *MarshalableRPCBlockNumberOrHash) String() string {
return (*rpc.BlockNumberOrHash)(m).String()
}
...@@ -15,7 +15,8 @@ import ( ...@@ -15,7 +15,8 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
const defaultL2GasLimit = 15_000_000 // defaultL2GasLimit represents the default gas limit for an L2 block.
const defaultL2GasLimit = 30_000_000
// NewL2Genesis will create a new L2 genesis // NewL2Genesis will create a new L2 genesis
func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, error) { func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, error) {
......
...@@ -28,12 +28,11 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) { ...@@ -28,12 +28,11 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) {
signer := types.LatestSigner(sd.L2Cfg.Config) signer := types.LatestSigner(sd.L2Cfg.Config)
cl := sequencerEngine.EthClient() cl := sequencerEngine.EthClient()
aliceNonce := uint64(0) // manual nonce management to avoid geth pending-tx nonce non-determinism flakiness
aliceTx := func() { aliceTx := func() {
n, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
require.NoError(t, err)
tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{
ChainID: sd.L2Cfg.Config.ChainID, ChainID: sd.L2Cfg.Config.ChainID,
Nonce: n, Nonce: aliceNonce,
GasTipCap: big.NewInt(2 * params.GWei), GasTipCap: big.NewInt(2 * params.GWei),
GasFeeCap: new(big.Int).Add(miner.l1Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)), GasFeeCap: new(big.Int).Add(miner.l1Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)),
Gas: params.TxGas, Gas: params.TxGas,
...@@ -41,6 +40,7 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) { ...@@ -41,6 +40,7 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) {
Value: e2eutils.Ether(2), Value: e2eutils.Ether(2),
}) })
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx)) require.NoError(gt, cl.SendTransaction(t.Ctx(), tx))
aliceNonce += 1
} }
makeL2BlockWithAliceTx := func() { makeL2BlockWithAliceTx := func() {
aliceTx() aliceTx()
...@@ -139,12 +139,11 @@ func TestLargeL1Gaps(gt *testing.T) { ...@@ -139,12 +139,11 @@ func TestLargeL1Gaps(gt *testing.T) {
signer := types.LatestSigner(sd.L2Cfg.Config) signer := types.LatestSigner(sd.L2Cfg.Config)
cl := sequencerEngine.EthClient() cl := sequencerEngine.EthClient()
aliceNonce := uint64(0) // manual nonce, avoid pending-tx nonce management, that causes flakes
aliceTx := func() { aliceTx := func() {
n, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
require.NoError(t, err)
tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{
ChainID: sd.L2Cfg.Config.ChainID, ChainID: sd.L2Cfg.Config.ChainID,
Nonce: n, Nonce: aliceNonce,
GasTipCap: big.NewInt(2 * params.GWei), GasTipCap: big.NewInt(2 * params.GWei),
GasFeeCap: new(big.Int).Add(miner.l1Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)), GasFeeCap: new(big.Int).Add(miner.l1Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)),
Gas: params.TxGas, Gas: params.TxGas,
...@@ -152,6 +151,7 @@ func TestLargeL1Gaps(gt *testing.T) { ...@@ -152,6 +151,7 @@ func TestLargeL1Gaps(gt *testing.T) {
Value: e2eutils.Ether(2), Value: e2eutils.Ether(2),
}) })
require.NoError(gt, cl.SendTransaction(t.Ctx(), tx)) require.NoError(gt, cl.SendTransaction(t.Ctx(), tx))
aliceNonce += 1
} }
makeL2BlockWithAliceTx := func() { makeL2BlockWithAliceTx := func() {
aliceTx() aliceTx()
......
...@@ -384,7 +384,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -384,7 +384,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
} }
// TestBigL2Txs tests a high-throughput case with constrained batcher: // TestBigL2Txs tests a high-throughput case with constrained batcher:
// - Fill 100 L2 blocks to near max-capacity, with txs of 120 KB each // - Fill 40 L2 blocks to near max-capacity, with txs of 120 KB each
// - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary // - Buffer the L2 blocks into channels together as much as possible, submit data-txs only when necessary
// (just before crossing the max RLP channel size) // (just before crossing the max RLP channel size)
// - Limit the data-tx size to 40 KB, to force data to be split across multiple datat-txs // - Limit the data-tx size to 40 KB, to force data to be split across multiple datat-txs
...@@ -428,7 +428,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -428,7 +428,7 @@ func TestBigL2Txs(gt *testing.T) {
} }
// build many L2 blocks filled to the brim with large txs of random data // build many L2 blocks filled to the brim with large txs of random data
for i := 0; i < 100; i++ { for i := 0; i < 40; i++ {
aliceNonce, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice) aliceNonce, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice)
status := sequencer.SyncStatus() status := sequencer.SyncStatus()
// build empty L1 blocks as necessary, so the L2 sequencer can continue to include txs while not drifting too far out // build empty L1 blocks as necessary, so the L2 sequencer can continue to include txs while not drifting too far out
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"errors" "errors"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi" "github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
......
...@@ -4,8 +4,8 @@ import ( ...@@ -4,8 +4,8 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi" "github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi"
"github.com/ethereum-optimism/optimism/op-program/l2/engineapi/test" "github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi/test"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
...@@ -191,7 +191,7 @@ func TestL2EngineAPIFail(gt *testing.T) { ...@@ -191,7 +191,7 @@ func TestL2EngineAPIFail(gt *testing.T) {
} }
func TestEngineAPITests(t *testing.T) { func TestEngineAPITests(t *testing.T) {
test.RunEngineAPITests(t, func() engineapi.EngineBackend { test.RunEngineAPITests(t, func(t *testing.T) engineapi.EngineBackend {
jwtPath := e2eutils.WriteDefaultJWT(t) jwtPath := e2eutils.WriteDefaultJWT(t)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
......
...@@ -49,3 +49,46 @@ func TestDerivationWithFlakyL1RPC(gt *testing.T) { ...@@ -49,3 +49,46 @@ func TestDerivationWithFlakyL1RPC(gt *testing.T) {
// Verifier should be synced, even though it hit lots of temporary L1 RPC errors // Verifier should be synced, even though it hit lots of temporary L1 RPC errors
require.Equal(t, sequencer.L2Unsafe(), verifier.L2Safe(), "verifier is synced") require.Equal(t, sequencer.L2Unsafe(), verifier.L2Safe(), "verifier is synced")
} }
func TestFinalizeWhileSyncing(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlError) // mute all the temporary derivation errors that we forcefully create
_, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
verifierStartStatus := verifier.SyncStatus()
// Build an L1 chain with 64 + 1 blocks, containing batches of L2 chain.
// Enough to go past the finalityDelay of the engine queue,
// to make the verifier finalize while it syncs.
miner.ActEmptyBlock(t)
for i := 0; i < 64+1; i++ {
sequencer.ActL1HeadSignal(t)
sequencer.ActL2PipelineFull(t)
sequencer.ActBuildToL1Head(t)
batcher.ActSubmitAll(t)
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(batcher.batcherAddr)(t)
miner.ActL1EndBlock(t)
}
l1Head := miner.l1Chain.CurrentHeader()
// finalize all of L1
miner.ActL1Safe(t, l1Head.Number.Uint64())
miner.ActL1Finalize(t, l1Head.Number.Uint64())
// Now signal L1 finality to the verifier, while the verifier is not synced.
verifier.ActL1HeadSignal(t)
verifier.ActL1SafeSignal(t)
verifier.ActL1FinalizedSignal(t)
// Now sync the verifier, without repeating the signal.
// While it's syncing, it should finalize on interval now, based on the future L1 finalized block it remembered.
verifier.ActL2PipelineFull(t)
// Verify the verifier finalized something new
require.Less(t, verifierStartStatus.FinalizedL2.Number, verifier.SyncStatus().FinalizedL2.Number, "verifier finalized L2 blocks during sync")
}
...@@ -20,10 +20,7 @@ import ( ...@@ -20,10 +20,7 @@ import (
// TestERC20BridgeDeposits tests the the L1StandardBridge bridge ERC20 // TestERC20BridgeDeposits tests the the L1StandardBridge bridge ERC20
// functionality. // functionality.
func TestERC20BridgeDeposits(t *testing.T) { func TestERC20BridgeDeposits(t *testing.T) {
parallel(t) InitParallel(t)
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
......
package op_e2e
import (
"flag"
"os"
"testing"
"github.com/ethereum/go-ethereum/log"
)
var enableParallelTesting bool = true
// Init testing to enable test flags
var _ = func() bool {
testing.Init()
return true
}()
var verboseGethNodes bool
func init() {
flag.BoolVar(&verboseGethNodes, "gethlogs", true, "Enable logs on geth nodes")
flag.Parse()
if os.Getenv("OP_E2E_DISABLE_PARALLEL") == "true" {
enableParallelTesting = false
}
}
func InitParallel(t *testing.T) {
t.Helper()
if enableParallelTesting {
t.Parallel()
}
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
}
...@@ -121,6 +121,7 @@ var hardcodedSlots = []storageSlot{ ...@@ -121,6 +121,7 @@ var hardcodedSlots = []storageSlot{
} }
func TestMigration(t *testing.T) { func TestMigration(t *testing.T) {
InitParallel(t)
if !config.enabled { if !config.enabled {
t.Skipf("skipping migration tests") t.Skipf("skipping migration tests")
return return
......
...@@ -100,7 +100,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e ...@@ -100,7 +100,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
SystemConfig: rollupGenesis.SystemConfig, SystemConfig: rollupGenesis.SystemConfig,
L1ChainConfig: l1Genesis.Config, L1ChainConfig: l1Genesis.Config,
L2ChainConfig: l2Genesis.Config, L2ChainConfig: l2Genesis.Config,
L1Head: l1Block, L1Head: eth.BlockToInfo(l1Block),
L2Head: genesisPayload, L2Head: genesisPayload,
}, nil }, nil
} }
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
// TestMissingGasLimit tests that op-geth cannot build a block without gas limit while optimism is active in the chain config. // TestMissingGasLimit tests that op-geth cannot build a block without gas limit while optimism is active in the chain config.
func TestMissingGasLimit(t *testing.T) { func TestMissingGasLimit(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
...@@ -42,6 +43,7 @@ func TestMissingGasLimit(t *testing.T) { ...@@ -42,6 +43,7 @@ func TestMissingGasLimit(t *testing.T) {
// TestInvalidDepositInFCU runs an invalid deposit through a FCU/GetPayload/NewPayload/FCU set of calls. // TestInvalidDepositInFCU runs an invalid deposit through a FCU/GetPayload/NewPayload/FCU set of calls.
// This tests that deposits must always allow the block to be built even if they are invalid. // This tests that deposits must always allow the block to be built even if they are invalid.
func TestInvalidDepositInFCU(t *testing.T) { func TestInvalidDepositInFCU(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FundDevAccounts = false cfg.DeployConfig.FundDevAccounts = false
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
...@@ -76,6 +78,7 @@ func TestInvalidDepositInFCU(t *testing.T) { ...@@ -76,6 +78,7 @@ func TestInvalidDepositInFCU(t *testing.T) {
} }
func TestPreregolith(t *testing.T) { func TestPreregolith(t *testing.T) {
InitParallel(t)
futureTimestamp := hexutil.Uint64(4) futureTimestamp := hexutil.Uint64(4)
tests := []struct { tests := []struct {
name string name string
...@@ -87,6 +90,7 @@ func TestPreregolith(t *testing.T) { ...@@ -87,6 +90,7 @@ func TestPreregolith(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run("GasUsed_"+test.name, func(t *testing.T) { t.Run("GasUsed_"+test.name, func(t *testing.T) {
InitParallel(t)
// Setup an L2 EE and create a client connection to the engine. // Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis. // We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
...@@ -135,6 +139,7 @@ func TestPreregolith(t *testing.T) { ...@@ -135,6 +139,7 @@ func TestPreregolith(t *testing.T) {
}) })
t.Run("DepositNonce_"+test.name, func(t *testing.T) { t.Run("DepositNonce_"+test.name, func(t *testing.T) {
InitParallel(t)
// Setup an L2 EE and create a client connection to the engine. // Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis. // We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
...@@ -193,6 +198,7 @@ func TestPreregolith(t *testing.T) { ...@@ -193,6 +198,7 @@ func TestPreregolith(t *testing.T) {
}) })
t.Run("UnusedGasConsumed_"+test.name, func(t *testing.T) { t.Run("UnusedGasConsumed_"+test.name, func(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
...@@ -234,6 +240,7 @@ func TestPreregolith(t *testing.T) { ...@@ -234,6 +240,7 @@ func TestPreregolith(t *testing.T) {
}) })
t.Run("AllowSystemTx_"+test.name, func(t *testing.T) { t.Run("AllowSystemTx_"+test.name, func(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime cfg.DeployConfig.L2GenesisRegolithTimeOffset = test.regolithTime
...@@ -255,6 +262,7 @@ func TestPreregolith(t *testing.T) { ...@@ -255,6 +262,7 @@ func TestPreregolith(t *testing.T) {
} }
func TestRegolith(t *testing.T) { func TestRegolith(t *testing.T) {
InitParallel(t)
tests := []struct { tests := []struct {
name string name string
regolithTime hexutil.Uint64 regolithTime hexutil.Uint64
...@@ -269,6 +277,7 @@ func TestRegolith(t *testing.T) { ...@@ -269,6 +277,7 @@ func TestRegolith(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run("GasUsedIsAccurate_"+test.name, func(t *testing.T) { t.Run("GasUsedIsAccurate_"+test.name, func(t *testing.T) {
InitParallel(t)
// Setup an L2 EE and create a client connection to the engine. // Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis. // We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
...@@ -320,6 +329,7 @@ func TestRegolith(t *testing.T) { ...@@ -320,6 +329,7 @@ func TestRegolith(t *testing.T) {
}) })
t.Run("DepositNonceCorrect_"+test.name, func(t *testing.T) { t.Run("DepositNonceCorrect_"+test.name, func(t *testing.T) {
InitParallel(t)
// Setup an L2 EE and create a client connection to the engine. // Setup an L2 EE and create a client connection to the engine.
// We also need to setup a L1 Genesis to create the rollup genesis. // We also need to setup a L1 Genesis to create the rollup genesis.
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
...@@ -381,6 +391,7 @@ func TestRegolith(t *testing.T) { ...@@ -381,6 +391,7 @@ func TestRegolith(t *testing.T) {
}) })
t.Run("ReturnUnusedGasToPool_"+test.name, func(t *testing.T) { t.Run("ReturnUnusedGasToPool_"+test.name, func(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
...@@ -423,6 +434,7 @@ func TestRegolith(t *testing.T) { ...@@ -423,6 +434,7 @@ func TestRegolith(t *testing.T) {
}) })
t.Run("RejectSystemTx_"+test.name, func(t *testing.T) { t.Run("RejectSystemTx_"+test.name, func(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime cfg.DeployConfig.L2GenesisRegolithTimeOffset = &test.regolithTime
...@@ -444,6 +456,7 @@ func TestRegolith(t *testing.T) { ...@@ -444,6 +456,7 @@ func TestRegolith(t *testing.T) {
}) })
t.Run("IncludeGasRefunds_"+test.name, func(t *testing.T) { t.Run("IncludeGasRefunds_"+test.name, func(t *testing.T) {
InitParallel(t)
// Simple constructor that is prefixed to the actual contract code // Simple constructor that is prefixed to the actual contract code
// Results in the contract code being returned as the code for the new contract // Results in the contract code being returned as the code for the new contract
deployPrefixSize := byte(16) deployPrefixSize := byte(16)
......
...@@ -68,7 +68,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -68,7 +68,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
deployConfig := &genesis.DeployConfig{ deployConfig := &genesis.DeployConfig{
L1ChainID: 900, L1ChainID: 900,
L2ChainID: 901, L2ChainID: 901,
L2BlockTime: 2, L2BlockTime: 1,
FinalizationPeriodSeconds: 60 * 60 * 24, FinalizationPeriodSeconds: 60 * 60 * 24,
MaxSequencerDrift: 10, MaxSequencerDrift: 10,
...@@ -233,6 +233,10 @@ type System struct { ...@@ -233,6 +233,10 @@ type System struct {
Mocknet mocknet.Mocknet Mocknet mocknet.Mocknet
} }
func (sys *System) NodeEndpoint(name string) string {
return selectEndpoint(sys.Nodes[name])
}
func (sys *System) Close() { func (sys *System) Close() {
if sys.L2OutputSubmitter != nil { if sys.L2OutputSubmitter != nil {
sys.L2OutputSubmitter.Stop() sys.L2OutputSubmitter.Stop()
...@@ -619,13 +623,17 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { ...@@ -619,13 +623,17 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) {
return sys, nil return sys, nil
} }
func configureL1(rollupNodeCfg *rollupNode.Config, l1Node *node.Node) { func selectEndpoint(node *node.Node) string {
l1EndpointConfig := l1Node.WSEndpoint()
useHTTP := os.Getenv("OP_E2E_USE_HTTP") == "true" useHTTP := os.Getenv("OP_E2E_USE_HTTP") == "true"
if useHTTP { if useHTTP {
log.Info("using HTTP client") log.Info("using HTTP client")
l1EndpointConfig = l1Node.HTTPEndpoint() return node.HTTPEndpoint()
} }
return node.WSEndpoint()
}
func configureL1(rollupNodeCfg *rollupNode.Config, l1Node *node.Node) {
l1EndpointConfig := selectEndpoint(l1Node)
rollupNodeCfg.L1 = &rollupNode.L1EndpointConfig{ rollupNodeCfg.L1 = &rollupNode.L1EndpointConfig{
L1NodeAddr: l1EndpointConfig, L1NodeAddr: l1EndpointConfig,
L1TrustRPC: false, L1TrustRPC: false,
......
package op_e2e
import (
"context"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
opp "github.com/ethereum-optimism/optimism/op-program/host"
oppconf "github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func TestVerifyL2OutputRoot(t *testing.T) {
InitParallel(t)
ctx := context.Background()
cfg := DefaultSystemConfig(t)
// We don't need a verifier - just the sequencer is enough
delete(cfg.Nodes, "verifier")
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
defer sys.Close()
log := testlog.Logger(t, log.LvlInfo)
log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time)
l1Client := sys.Clients["l1"]
l2Seq := sys.Clients["sequencer"]
rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint())
require.Nil(t, err)
rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient))
// TODO (CLI-3855): Actually perform some tx to set up a more complex chain.
// Wait for the safe head to reach block 10
require.NoError(t, waitForSafeHead(ctx, 10, rollupClient))
// Use block 5 as the agreed starting block on L2
l2AgreedBlock, err := l2Seq.BlockByNumber(ctx, big.NewInt(5))
require.NoError(t, err, "could not retrieve l2 genesis")
l2Head := l2AgreedBlock.Hash() // Agreed starting L2 block
// Get the expected output at block 10
l2ClaimBlockNumber := uint64(10)
l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber)
require.NoError(t, err, "could not get expected output")
l2Claim := l2Output.OutputRoot
// Find the current L1 head
l1BlockNumber, err := l1Client.BlockNumber(ctx)
require.NoError(t, err, "get l1 head block number")
l1HeadBlock, err := l1Client.BlockByNumber(ctx, new(big.Int).SetUint64(l1BlockNumber))
require.NoError(t, err, "get l1 head block")
l1Head := l1HeadBlock.Hash()
preimageDir := t.TempDir()
fppConfig := oppconf.NewConfig(sys.RollupConfig, sys.L2GenesisCfg.Config, l1Head, l2Head, common.Hash(l2Claim), l2ClaimBlockNumber)
fppConfig.L1URL = sys.NodeEndpoint("l1")
fppConfig.L2URL = sys.NodeEndpoint("sequencer")
fppConfig.DataDir = preimageDir
// Check the FPP confirms the expected output
t.Log("Running fault proof in fetching mode")
err = opp.FaultProofProgram(log, fppConfig)
require.NoError(t, err)
// Shutdown the nodes from the actual chain. Should now be able to run using only the pre-fetched data.
for _, node := range sys.Nodes {
require.NoError(t, node.Close())
}
t.Log("Running fault proof in offline mode")
// Should be able to rerun in offline mode using the pre-fetched images
fppConfig.L1URL = ""
fppConfig.L2URL = ""
err = opp.FaultProofProgram(log, fppConfig)
require.NoError(t, err)
// Check that a fault is detected if we provide an incorrect claim
t.Log("Running fault proof with invalid claim")
fppConfig.L2Claim = common.Hash{0xaa}
err = opp.FaultProofProgram(log, fppConfig)
require.ErrorIs(t, err, opp.ErrClaimNotValid)
}
func waitForSafeHead(ctx context.Context, safeBlockNum uint64, rollupClient *sources.RollupClient) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
for {
seqStatus, err := rollupClient.SyncStatus(ctx)
if err != nil {
return err
}
if seqStatus.SafeL2.Number >= safeBlockNum {
return nil
}
}
}
This diff is collapsed.
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
...@@ -33,18 +32,13 @@ import ( ...@@ -33,18 +32,13 @@ import (
// TestGasPriceOracleFeeUpdates checks that the gas price oracle cannot be locked by mis-configuring parameters. // TestGasPriceOracleFeeUpdates checks that the gas price oracle cannot be locked by mis-configuring parameters.
func TestGasPriceOracleFeeUpdates(t *testing.T) { func TestGasPriceOracleFeeUpdates(t *testing.T) {
parallel(t) InitParallel(t)
// Define our values to set in the GasPriceOracle (we set them high to see if it can lock L2 or stop bindings // Define our values to set in the GasPriceOracle (we set them high to see if it can lock L2 or stop bindings
// from updating the prices once again. // from updating the prices once again.
overheadValue := abi.MaxUint256 overheadValue := abi.MaxUint256
scalarValue := abi.MaxUint256 scalarValue := abi.MaxUint256
var cancel context.CancelFunc var cancel context.CancelFunc
// Setup our logger handler
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
// Create our system configuration for L1/L2 and start it // Create our system configuration for L1/L2 and start it
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
sys, err := cfg.Start() sys, err := cfg.Start()
...@@ -126,11 +120,7 @@ func TestGasPriceOracleFeeUpdates(t *testing.T) { ...@@ -126,11 +120,7 @@ func TestGasPriceOracleFeeUpdates(t *testing.T) {
// TestL2SequencerRPCDepositTx checks that the L2 sequencer will not accept DepositTx type transactions. // TestL2SequencerRPCDepositTx checks that the L2 sequencer will not accept DepositTx type transactions.
// The acceptance of these transactions would allow for arbitrary minting of ETH in L2. // The acceptance of these transactions would allow for arbitrary minting of ETH in L2.
func TestL2SequencerRPCDepositTx(t *testing.T) { func TestL2SequencerRPCDepositTx(t *testing.T) {
parallel(t) InitParallel(t)
// Setup our logger handler
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
// Create our system configuration for L1/L2 and start it // Create our system configuration for L1/L2 and start it
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
...@@ -233,7 +223,7 @@ func startConfigWithTestAccounts(cfg *SystemConfig, accountsToGenerate int) (*Sy ...@@ -233,7 +223,7 @@ func startConfigWithTestAccounts(cfg *SystemConfig, accountsToGenerate int) (*Sy
// TestMixedDepositValidity makes a number of deposit transactions, some which will succeed in transferring value, // TestMixedDepositValidity makes a number of deposit transactions, some which will succeed in transferring value,
// while others do not. It ensures that the expected nonces/balances match after several interactions. // while others do not. It ensures that the expected nonces/balances match after several interactions.
func TestMixedDepositValidity(t *testing.T) { func TestMixedDepositValidity(t *testing.T) {
parallel(t) InitParallel(t)
// Define how many deposit txs we'll make. Each deposit mints a fixed amount and transfers up to 1/3 of the user's // Define how many deposit txs we'll make. Each deposit mints a fixed amount and transfers up to 1/3 of the user's
// balance. As such, this number cannot be too high or else the test will always fail due to lack of balance in L1. // balance. As such, this number cannot be too high or else the test will always fail due to lack of balance in L1.
const depositTxCount = 15 const depositTxCount = 15
...@@ -241,11 +231,6 @@ func TestMixedDepositValidity(t *testing.T) { ...@@ -241,11 +231,6 @@ func TestMixedDepositValidity(t *testing.T) {
// Define how many accounts we'll use to deposit funds // Define how many accounts we'll use to deposit funds
const accountUsedToDeposit = 5 const accountUsedToDeposit = 5
// Setup our logger handler
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
// Create our system configuration, funding all accounts we created for L1/L2, and start it // Create our system configuration, funding all accounts we created for L1/L2, and start it
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
sys, testAccounts, err := startConfigWithTestAccounts(&cfg, accountUsedToDeposit) sys, testAccounts, err := startConfigWithTestAccounts(&cfg, accountUsedToDeposit)
...@@ -415,16 +400,14 @@ func TestMixedDepositValidity(t *testing.T) { ...@@ -415,16 +400,14 @@ func TestMixedDepositValidity(t *testing.T) {
// TestMixedWithdrawalValidity makes a number of withdrawal transactions and ensures ones with modified parameters are // TestMixedWithdrawalValidity makes a number of withdrawal transactions and ensures ones with modified parameters are
// rejected while unmodified ones are accepted. This runs test cases in different systems. // rejected while unmodified ones are accepted. This runs test cases in different systems.
func TestMixedWithdrawalValidity(t *testing.T) { func TestMixedWithdrawalValidity(t *testing.T) {
parallel(t) InitParallel(t)
// Setup our logger handler
if !verboseGethNodes {
log.Root().SetHandler(log.DiscardHandler())
}
// There are 7 different fields we try modifying to cause a failure, plus one "good" test result we test. // There are 7 different fields we try modifying to cause a failure, plus one "good" test result we test.
for i := 0; i <= 8; i++ { for i := 0; i <= 8; i++ {
i := i // avoid loop var capture i := i // avoid loop var capture
t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) { t.Run(fmt.Sprintf("withdrawal test#%d", i+1), func(t *testing.T) {
InitParallel(t)
// Create our system configuration, funding all accounts we created for L1/L2, and start it // Create our system configuration, funding all accounts we created for L1/L2, and start it
cfg := DefaultSystemConfig(t) cfg := DefaultSystemConfig(t)
cfg.DeployConfig.FinalizationPeriodSeconds = 6 cfg.DeployConfig.FinalizationPeriodSeconds = 6
......
package op_e2e
import (
"context"
"crypto/ecdsa"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
// SendDepositTx creates and sends a deposit transaction.
// The L1 transaction, including sender, is configured by the l1Opts param.
// The L2 transaction options can be configured by modifying the DepositTxOps value supplied to applyL2Opts
// Will verify that the transaction is included with the expected status on L1 and L2
func SendDepositTx(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Client *ethclient.Client, l1Opts *bind.TransactOpts, applyL2Opts DepositTxOptsFn) {
l2Opts := defaultDepositTxOpts(l1Opts)
applyL2Opts(l2Opts)
// Find deposit contract
depositContract, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
require.Nil(t, err)
// Finally send TX
tx, err := depositContract.DepositTransaction(l1Opts, l2Opts.ToAddr, l2Opts.Value, l2Opts.GasLimit, l2Opts.IsCreation, l2Opts.Data)
require.Nil(t, err, "with deposit tx")
// Wait for transaction on L1
receipt, err := waitForTransaction(tx.Hash(), l1Client, 10*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "Waiting for deposit tx on L1")
// Wait for transaction to be included on L2
reconstructedDep, err := derive.UnmarshalDepositLogEvent(receipt.Logs[0])
require.NoError(t, err, "Could not reconstruct L2 Deposit")
tx = types.NewTx(reconstructedDep)
receipt, err = waitForTransaction(tx.Hash(), l2Client, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second)
require.NoError(t, err)
require.Equal(t, l2Opts.ExpectedStatus, receipt.Status)
}
type DepositTxOptsFn func(l2Opts *DepositTxOpts)
type DepositTxOpts struct {
ToAddr common.Address
Value *big.Int
GasLimit uint64
IsCreation bool
Data []byte
ExpectedStatus uint64
}
func defaultDepositTxOpts(opts *bind.TransactOpts) *DepositTxOpts {
return &DepositTxOpts{
ToAddr: opts.From,
Value: opts.Value,
GasLimit: 1_000_000,
IsCreation: false,
Data: nil,
ExpectedStatus: types.ReceiptStatusSuccessful,
}
}
// SendL2Tx creates and sends a transaction.
// The supplied privKey is used to specify the account to send from and the transaction is sent to the supplied l2Client
// Transaction options and expected status can be configured in the applyTxOpts function by modifying the supplied TxOpts
// Will verify that the transaction is included with the expected status on l2Client and any clients added to TxOpts.VerifyClients
func SendL2Tx(t *testing.T, cfg SystemConfig, l2Client *ethclient.Client, privKey *ecdsa.PrivateKey, applyTxOpts TxOptsFn) *types.Receipt {
opts := defaultTxOpts()
applyTxOpts(opts)
tx := types.MustSignNewTx(privKey, types.LatestSignerForChainID(cfg.L2ChainIDBig()), &types.DynamicFeeTx{
ChainID: cfg.L2ChainIDBig(),
Nonce: opts.Nonce, // Already have deposit
To: opts.ToAddr,
Value: opts.Value,
GasTipCap: opts.GasTipCap,
GasFeeCap: opts.GasFeeCap,
Gas: opts.Gas,
})
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := l2Client.SendTransaction(ctx, tx)
require.Nil(t, err, "Sending L2 tx")
receipt, err := waitForTransaction(tx.Hash(), l2Client, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second)
require.Nil(t, err, "Waiting for L2 tx")
require.Equal(t, opts.ExpectedStatus, receipt.Status, "TX should have expected status")
for i, client := range opts.VerifyClients {
t.Logf("Waiting for tx %v on verification client %d", tx.Hash(), i)
receiptVerif, err := waitForTransaction(tx.Hash(), client, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second)
require.Nilf(t, err, "Waiting for L2 tx on verification client %d", i)
require.Equalf(t, receipt, receiptVerif, "Receipts should be the same on sequencer and verification client %d", i)
}
return receipt
}
type TxOptsFn func(opts *TxOpts)
type TxOpts struct {
ToAddr *common.Address
Nonce uint64
Value *big.Int
Gas uint64
GasTipCap *big.Int
GasFeeCap *big.Int
Data []byte
ExpectedStatus uint64
VerifyClients []*ethclient.Client
}
// VerifyOnClients adds additional l2 clients that should sync the block the tx is included in
// Checks that the receipt received from these clients is equal to the receipt received from the sequencer
func (o *TxOpts) VerifyOnClients(clients ...*ethclient.Client) {
o.VerifyClients = append(o.VerifyClients, clients...)
}
func defaultTxOpts() *TxOpts {
return &TxOpts{
ToAddr: nil,
Nonce: 0,
Value: common.Big0,
GasTipCap: big.NewInt(10),
GasFeeCap: big.NewInt(200),
Gas: 21_000,
Data: nil,
ExpectedStatus: types.ReceiptStatusSuccessful,
}
}
package op_e2e
import (
"context"
"crypto/ecdsa"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func SendWithdrawal(t *testing.T, cfg SystemConfig, l2Client *ethclient.Client, privKey *ecdsa.PrivateKey, applyOpts WithdrawalTxOptsFn) (*types.Transaction, *types.Receipt) {
opts := defaultWithdrawalTxOpts()
applyOpts(opts)
// Bind L2 Withdrawer Contract
l2withdrawer, err := bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, l2Client)
require.Nil(t, err, "binding withdrawer on L2")
// Initiate Withdrawal
l2opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L2ChainIDBig())
require.Nil(t, err)
l2opts.Value = opts.Value
tx, err := l2withdrawer.InitiateWithdrawal(l2opts, l2opts.From, big.NewInt(int64(opts.Gas)), opts.Data)
require.Nil(t, err, "sending initiate withdraw tx")
receipt, err := waitForTransaction(tx.Hash(), l2Client, 10*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "withdrawal initiated on L2 sequencer")
require.Equal(t, opts.ExpectedStatus, receipt.Status, "transaction had incorrect status")
for i, client := range opts.VerifyClients {
t.Logf("Waiting for tx %v on verification client %d", tx.Hash(), i)
receiptVerif, err := waitForTransaction(tx.Hash(), client, 10*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second)
require.Nilf(t, err, "Waiting for L2 tx on verification client %d", i)
require.Equalf(t, receipt, receiptVerif, "Receipts should be the same on sequencer and verification client %d", i)
}
return tx, receipt
}
type WithdrawalTxOptsFn func(opts *WithdrawalTxOpts)
type WithdrawalTxOpts struct {
ToAddr *common.Address
Nonce uint64
Value *big.Int
Gas uint64
Data []byte
ExpectedStatus uint64
VerifyClients []*ethclient.Client
}
// VerifyOnClients adds additional l2 clients that should sync the block the tx is included in
// Checks that the receipt received from these clients is equal to the receipt received from the sequencer
func (o *WithdrawalTxOpts) VerifyOnClients(clients ...*ethclient.Client) {
o.VerifyClients = append(o.VerifyClients, clients...)
}
func defaultWithdrawalTxOpts() *WithdrawalTxOpts {
return &WithdrawalTxOpts{
ToAddr: nil,
Nonce: 0,
Value: common.Big0,
Gas: 21_000,
Data: nil,
ExpectedStatus: types.ReceiptStatusSuccessful,
}
}
func ProveAndFinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Node *node.Node, ethPrivKey *ecdsa.PrivateKey, l2WithdrawalReceipt *types.Receipt) (*types.Receipt, *types.Receipt) {
params, proveReceipt := ProveWithdrawal(t, cfg, l1Client, l2Node, ethPrivKey, l2WithdrawalReceipt)
finalizeReceipt := FinalizeWithdrawal(t, cfg, l1Client, ethPrivKey, l2WithdrawalReceipt, params)
return proveReceipt, finalizeReceipt
}
func ProveWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Node *node.Node, ethPrivKey *ecdsa.PrivateKey, l2WithdrawalReceipt *types.Receipt) (withdrawals.ProvenWithdrawalParameters, *types.Receipt) {
// Get l2BlockNumber for proof generation
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, l2WithdrawalReceipt.BlockNumber)
require.Nil(t, err)
rpcClient, err := rpc.Dial(l2Node.WSEndpoint())
require.Nil(t, err)
proofCl := gethclient.New(rpcClient)
receiptCl := ethclient.NewClient(rpcClient)
ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Get the latest header
header, err := receiptCl.HeaderByNumber(ctx, new(big.Int).SetUint64(blockNumber))
require.Nil(t, err)
// Now create withdrawal
oracle, err := bindings.NewL2OutputOracleCaller(predeploys.DevL2OutputOracleAddr, l1Client)
require.Nil(t, err)
params, err := withdrawals.ProveWithdrawalParameters(context.Background(), proofCl, receiptCl, l2WithdrawalReceipt.TxHash, header, oracle)
require.Nil(t, err)
portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
require.Nil(t, err)
opts, err := bind.NewKeyedTransactorWithChainID(ethPrivKey, cfg.L1ChainIDBig())
require.Nil(t, err)
// Prove withdrawal
tx, err := portal.ProveWithdrawalTransaction(
opts,
bindings.TypesWithdrawalTransaction{
Nonce: params.Nonce,
Sender: params.Sender,
Target: params.Target,
Value: params.Value,
GasLimit: params.GasLimit,
Data: params.Data,
},
params.L2OutputIndex,
params.OutputRootProof,
params.WithdrawalProof,
)
require.Nil(t, err)
// Ensure that our withdrawal was proved successfully
proveReceipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "prove withdrawal")
require.Equal(t, types.ReceiptStatusSuccessful, proveReceipt.Status)
return params, proveReceipt
}
func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, privKey *ecdsa.PrivateKey, withdrawalReceipt *types.Receipt, params withdrawals.ProvenWithdrawalParameters) *types.Receipt {
// Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel()
_, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, withdrawalReceipt.BlockNumber)
require.Nil(t, err)
opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L1ChainIDBig())
require.Nil(t, err)
portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
require.Nil(t, err)
// Finalize withdrawal
tx, err := portal.FinalizeWithdrawalTransaction(
opts,
bindings.TypesWithdrawalTransaction{
Nonce: params.Nonce,
Sender: params.Sender,
Target: params.Target,
Value: params.Value,
GasLimit: params.GasLimit,
Data: params.Data,
},
)
require.Nil(t, err)
// Ensure that our withdrawal was finalized successfully
finalizeReceipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "finalize withdrawal")
require.Equal(t, types.ReceiptStatusSuccessful, finalizeReceipt.Status)
return finalizeReceipt
}
...@@ -5,10 +5,9 @@ import ( ...@@ -5,10 +5,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
) )
var _ BlockInfo = (&types.Block{})
type BlockInfo interface { type BlockInfo interface {
Hash() common.Hash Hash() common.Hash
ParentHash() common.Hash ParentHash() common.Hash
...@@ -21,6 +20,10 @@ type BlockInfo interface { ...@@ -21,6 +20,10 @@ type BlockInfo interface {
BaseFee() *big.Int BaseFee() *big.Int
ReceiptHash() common.Hash ReceiptHash() common.Hash
GasUsed() uint64 GasUsed() uint64
// HeaderRLP returns the RLP of the block header as per consensus rules
// Returns an error if the header RLP could not be written
HeaderRLP() ([]byte, error)
} }
func InfoToL1BlockRef(info BlockInfo) L1BlockRef { func InfoToL1BlockRef(info BlockInfo) L1BlockRef {
...@@ -44,6 +47,19 @@ func ToBlockID(b NumberAndHash) BlockID { ...@@ -44,6 +47,19 @@ func ToBlockID(b NumberAndHash) BlockID {
} }
} }
// blockInfo is a conversion type of types.Block turning it into a BlockInfo
type blockInfo struct{ *types.Block }
func (b blockInfo) HeaderRLP() ([]byte, error) {
return rlp.EncodeToBytes(b.Header())
}
func BlockToInfo(b *types.Block) BlockInfo {
return blockInfo{b}
}
var _ BlockInfo = (*blockInfo)(nil)
// headerBlockInfo is a conversion type of types.Header turning it into a // headerBlockInfo is a conversion type of types.Header turning it into a
// BlockInfo. // BlockInfo.
type headerBlockInfo struct{ *types.Header } type headerBlockInfo struct{ *types.Header }
...@@ -84,6 +100,10 @@ func (h headerBlockInfo) GasUsed() uint64 { ...@@ -84,6 +100,10 @@ func (h headerBlockInfo) GasUsed() uint64 {
return h.Header.GasUsed return h.Header.GasUsed
} }
func (h headerBlockInfo) HeaderRLP() ([]byte, error) {
return rlp.EncodeToBytes(h.Header)
}
// HeaderBlockInfo returns h as a BlockInfo implementation. // HeaderBlockInfo returns h as a BlockInfo implementation.
func HeaderBlockInfo(h *types.Header) BlockInfo { func HeaderBlockInfo(h *types.Header) BlockInfo {
return headerBlockInfo{h} return headerBlockInfo{h}
......
package eth
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
// EncodeReceipts encodes a list of receipts into raw receipts. Some non-consensus meta-data may be lost.
func EncodeReceipts(elems []*types.Receipt) ([]hexutil.Bytes, error) {
out := make([]hexutil.Bytes, len(elems))
for i, el := range elems {
dat, err := el.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal receipt %d: %w", i, err)
}
out[i] = dat
}
return out, nil
}
// DecodeRawReceipts decodes receipts and adds additional blocks metadata.
// The contract-deployment addresses are not set however (high cost, depends on nonce values, unused by op-node).
func DecodeRawReceipts(block BlockID, rawReceipts []hexutil.Bytes, txHashes []common.Hash) ([]*types.Receipt, error) {
result := make([]*types.Receipt, len(rawReceipts))
totalIndex := uint(0)
prevCumulativeGasUsed := uint64(0)
for i, r := range rawReceipts {
var x types.Receipt
if err := x.UnmarshalBinary(r); err != nil {
return nil, fmt.Errorf("failed to decode receipt %d: %w", i, err)
}
x.TxHash = txHashes[i]
x.BlockHash = block.Hash
x.BlockNumber = new(big.Int).SetUint64(block.Number)
x.TransactionIndex = uint(i)
x.GasUsed = x.CumulativeGasUsed - prevCumulativeGasUsed
// contract address meta-data is not computed.
prevCumulativeGasUsed = x.CumulativeGasUsed
for _, l := range x.Logs {
l.BlockNumber = block.Number
l.TxHash = x.TxHash
l.TxIndex = uint(i)
l.BlockHash = block.Hash
l.Index = totalIndex
totalIndex += 1
}
result[i] = &x
}
return result, nil
}
package eth
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
// EncodeTransactions encodes a list of transactions into opaque transactions.
func EncodeTransactions(elems []*types.Transaction) ([]hexutil.Bytes, error) {
out := make([]hexutil.Bytes, len(elems))
for i, el := range elems {
dat, err := el.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal tx %d: %w", i, err)
}
out[i] = dat
}
return out, nil
}
// DecodeTransactions decodes a list of opaque transactions into transactions.
func DecodeTransactions(data []hexutil.Bytes) ([]*types.Transaction, error) {
dest := make([]*types.Transaction, len(data))
for i := range dest {
var x types.Transaction
if err := x.UnmarshalBinary(data[i]); err != nil {
return nil, fmt.Errorf("failed to unmarshal tx %d: %w", i, err)
}
dest[i] = &x
}
return dest, nil
}
// TransactionsToHashes computes the transaction-hash for every transaction in the input.
func TransactionsToHashes(elems []*types.Transaction) []common.Hash {
out := make([]common.Hash, len(elems))
for i, el := range elems {
out[i] = el.Hash()
}
return out
}
...@@ -256,28 +256,10 @@ func init() { ...@@ -256,28 +256,10 @@ func init() {
} }
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
l1NodeAddr := ctx.GlobalString(L1NodeAddr.Name) for _, f := range requiredFlags {
if l1NodeAddr == "" { if !ctx.GlobalIsSet(f.GetName()) {
return fmt.Errorf("flag %s is required", L1NodeAddr.Name) return fmt.Errorf("flag %s is required", f.GetName())
} }
l2EngineAddr := ctx.GlobalString(L2EngineAddr.Name)
if l2EngineAddr == "" {
return fmt.Errorf("flag %s is required", L2EngineAddr.Name)
}
rollupConfig := ctx.GlobalString(RollupConfig.Name)
network := ctx.GlobalString(Network.Name)
if rollupConfig == "" && network == "" {
return fmt.Errorf("flag %s or %s is required", RollupConfig.Name, Network.Name)
}
if rollupConfig != "" && network != "" {
return fmt.Errorf("cannot specify both %s and %s", RollupConfig.Name, Network.Name)
}
rpcListenAddr := ctx.GlobalString(RPCListenAddr.Name)
if rpcListenAddr == "" {
return fmt.Errorf("flag %s is required", RPCListenAddr.Name)
}
if !ctx.GlobalIsSet(RPCListenPort.Name) {
return fmt.Errorf("flag %s is required", RPCListenPort.Name)
} }
return nil return nil
} }
...@@ -64,6 +64,7 @@ type Metricer interface { ...@@ -64,6 +64,7 @@ type Metricer interface {
RecordSequencerBuildingDiffTime(duration time.Duration) RecordSequencerBuildingDiffTime(duration time.Duration)
RecordSequencerSealingTime(duration time.Duration) RecordSequencerSealingTime(duration time.Duration)
Document() []metrics.DocumentedMetric Document() []metrics.DocumentedMetric
RecordChannelInputBytes(num int)
// P2P Metrics // P2P Metrics
SetPeerScores(scores map[string]float64) SetPeerScores(scores map[string]float64)
ClientPayloadByNumberEvent(num uint64, resultCode byte, duration time.Duration) ClientPayloadByNumberEvent(num uint64, resultCode byte, duration time.Duration)
...@@ -131,6 +132,8 @@ type Metrics struct { ...@@ -131,6 +132,8 @@ type Metrics struct {
GossipEventsTotal *prometheus.CounterVec GossipEventsTotal *prometheus.CounterVec
BandwidthTotal *prometheus.GaugeVec BandwidthTotal *prometheus.GaugeVec
ChannelInputBytes prometheus.Counter
registry *prometheus.Registry registry *prometheus.Registry
factory metrics.Factory factory metrics.Factory
} }
...@@ -331,6 +334,12 @@ func NewMetrics(procName string) *Metrics { ...@@ -331,6 +334,12 @@ func NewMetrics(procName string) *Metrics {
"direction", "direction",
}), }),
ChannelInputBytes: factory.NewCounter(prometheus.CounterOpts{
Namespace: ns,
Name: "channel_input_bytes",
Help: "Number of compressed bytes added to the channel",
}),
P2PReqDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{ P2PReqDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns, Namespace: ns,
Subsystem: "p2p", Subsystem: "p2p",
...@@ -635,6 +644,10 @@ func (m *Metrics) PayloadsQuarantineSize(n int) { ...@@ -635,6 +644,10 @@ func (m *Metrics) PayloadsQuarantineSize(n int) {
m.PayloadsQuarantineTotal.Set(float64(n)) m.PayloadsQuarantineTotal.Set(float64(n))
} }
func (m *Metrics) RecordChannelInputBytes(inputCompressedBytes int) {
m.ChannelInputBytes.Add(float64(inputCompressedBytes))
}
type noopMetricer struct{} type noopMetricer struct{}
var NoopMetrics Metricer = new(noopMetricer) var NoopMetrics Metricer = new(noopMetricer)
...@@ -737,3 +750,6 @@ func (n *noopMetricer) ServerPayloadByNumberEvent(num uint64, resultCode byte, d ...@@ -737,3 +750,6 @@ func (n *noopMetricer) ServerPayloadByNumberEvent(num uint64, resultCode byte, d
func (n *noopMetricer) PayloadsQuarantineSize(int) { func (n *noopMetricer) PayloadsQuarantineSize(int) {
} }
func (n *noopMetricer) RecordChannelInputBytes(int) {
}
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
...@@ -115,12 +114,7 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number hexutil.Uint64) (*et ...@@ -115,12 +114,7 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number hexutil.Uint64) (*et
} }
var l2OutputRootVersion eth.Bytes32 // it's zero for now var l2OutputRootVersion eth.Bytes32 // it's zero for now
l2OutputRoot, err := rollup.ComputeL2OutputRoot(&bindings.TypesOutputRootProof{ l2OutputRoot, err := rollup.ComputeL2OutputRootV0(head, proof.StorageHash)
Version: l2OutputRootVersion,
StateRoot: head.Root(),
MessagePasserStorageRoot: proof.StorageHash,
LatestBlockhash: head.Hash(),
})
if err != nil { if err != nil {
n.log.Error("Error computing L2 output root, nil ptr passed to hashing function") n.log.Error("Error computing L2 output root, nil ptr passed to hashing function")
return nil, err return nil, err
......
...@@ -54,7 +54,8 @@ type GossipSetupConfigurables interface { ...@@ -54,7 +54,8 @@ type GossipSetupConfigurables interface {
PeerScoringParams() *pubsub.PeerScoreParams PeerScoringParams() *pubsub.PeerScoreParams
TopicScoringParams() *pubsub.TopicScoreParams TopicScoringParams() *pubsub.TopicScoreParams
BanPeers() bool BanPeers() bool
ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option // ConfigureGossip creates configuration options to apply to the GossipSub setup
ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option
PeerBandScorer() *BandScoreThresholds PeerBandScorer() *BandScoreThresholds
} }
...@@ -124,7 +125,10 @@ func BuildMsgIdFn(cfg *rollup.Config) pubsub.MsgIdFunction { ...@@ -124,7 +125,10 @@ func BuildMsgIdFn(cfg *rollup.Config) pubsub.MsgIdFunction {
} }
} }
func (p *Config) ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option { func (p *Config) ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option {
params := BuildGlobalGossipParams(rollupCfg)
// override with CLI changes
params.D = p.MeshD params.D = p.MeshD
params.Dlo = p.MeshDLo params.Dlo = p.MeshDLo
params.Dhi = p.MeshDHi params.Dhi = p.MeshDHi
...@@ -132,6 +136,7 @@ func (p *Config) ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option ...@@ -132,6 +136,7 @@ func (p *Config) ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option
// in the future we may add more advanced options like scoring and PX / direct-mesh / episub // in the future we may add more advanced options like scoring and PX / direct-mesh / episub
return []pubsub.Option{ return []pubsub.Option{
pubsub.WithGossipSubParams(params),
pubsub.WithFloodPublish(p.FloodPublish), pubsub.WithFloodPublish(p.FloodPublish),
} }
} }
...@@ -157,7 +162,6 @@ func NewGossipSub(p2pCtx context.Context, h host.Host, g ConnectionGater, cfg *r ...@@ -157,7 +162,6 @@ func NewGossipSub(p2pCtx context.Context, h host.Host, g ConnectionGater, cfg *r
if err != nil { if err != nil {
return nil, err return nil, err
} }
params := BuildGlobalGossipParams(cfg)
gossipOpts := []pubsub.Option{ gossipOpts := []pubsub.Option{
pubsub.WithMaxMessageSize(maxGossipSize), pubsub.WithMaxMessageSize(maxGossipSize),
pubsub.WithMessageIdFn(BuildMsgIdFn(cfg)), pubsub.WithMessageIdFn(BuildMsgIdFn(cfg)),
...@@ -170,11 +174,10 @@ func NewGossipSub(p2pCtx context.Context, h host.Host, g ConnectionGater, cfg *r ...@@ -170,11 +174,10 @@ func NewGossipSub(p2pCtx context.Context, h host.Host, g ConnectionGater, cfg *r
pubsub.WithSeenMessagesTTL(seenMessagesTTL), pubsub.WithSeenMessagesTTL(seenMessagesTTL),
pubsub.WithPeerExchange(false), pubsub.WithPeerExchange(false),
pubsub.WithBlacklist(denyList), pubsub.WithBlacklist(denyList),
pubsub.WithGossipSubParams(params),
pubsub.WithEventTracer(&gossipTracer{m: m}), pubsub.WithEventTracer(&gossipTracer{m: m}),
} }
gossipOpts = append(gossipOpts, ConfigurePeerScoring(h, g, gossipConf, m, log)...) gossipOpts = append(gossipOpts, ConfigurePeerScoring(h, g, gossipConf, m, log)...)
gossipOpts = append(gossipOpts, gossipConf.ConfigureGossip(&params)...) gossipOpts = append(gossipOpts, gossipConf.ConfigureGossip(cfg)...)
return pubsub.NewGossipSub(p2pCtx, h, gossipOpts...) return pubsub.NewGossipSub(p2pCtx, h, gossipOpts...)
} }
......
...@@ -62,8 +62,10 @@ func (p *Prepared) Discovery(log log.Logger, rollupCfg *rollup.Config, tcpPort u ...@@ -62,8 +62,10 @@ func (p *Prepared) Discovery(log log.Logger, rollupCfg *rollup.Config, tcpPort u
return p.LocalNode, p.UDPv5, nil return p.LocalNode, p.UDPv5, nil
} }
func (p *Prepared) ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option { func (p *Prepared) ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option {
return nil return []pubsub.Option{
pubsub.WithGossipSubParams(BuildGlobalGossipParams(rollupCfg)),
}
} }
func (p *Prepared) PeerScoringParams() *pubsub.PeerScoreParams { func (p *Prepared) PeerScoringParams() *pubsub.PeerScoreParams {
......
...@@ -124,6 +124,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) { ...@@ -124,6 +124,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) {
if !ch.IsReady() { if !ch.IsReady() {
return nil, io.EOF return nil, io.EOF
} }
cb.log.Info("Reading channel", "channel", first, "frames", len(ch.inputs))
delete(cb.channels, first) delete(cb.channels, first)
cb.channelQueue = cb.channelQueue[1:] cb.channelQueue = cb.channelQueue[1:]
......
...@@ -21,15 +21,18 @@ type ChannelInReader struct { ...@@ -21,15 +21,18 @@ type ChannelInReader struct {
nextBatchFn func() (BatchWithL1InclusionBlock, error) nextBatchFn func() (BatchWithL1InclusionBlock, error)
prev *ChannelBank prev *ChannelBank
metrics Metrics
} }
var _ ResetableStage = (*ChannelInReader)(nil) var _ ResetableStage = (*ChannelInReader)(nil)
// NewChannelInReader creates a ChannelInReader, which should be Reset(origin) before use. // NewChannelInReader creates a ChannelInReader, which should be Reset(origin) before use.
func NewChannelInReader(log log.Logger, prev *ChannelBank) *ChannelInReader { func NewChannelInReader(log log.Logger, prev *ChannelBank, metrics Metrics) *ChannelInReader {
return &ChannelInReader{ return &ChannelInReader{
log: log, log: log,
prev: prev, prev: prev,
metrics: metrics,
} }
} }
...@@ -41,6 +44,7 @@ func (cr *ChannelInReader) Origin() eth.L1BlockRef { ...@@ -41,6 +44,7 @@ func (cr *ChannelInReader) Origin() eth.L1BlockRef {
func (cr *ChannelInReader) WriteChannel(data []byte) error { func (cr *ChannelInReader) WriteChannel(data []byte) error {
if f, err := BatchReader(bytes.NewBuffer(data), cr.Origin()); err == nil { if f, err := BatchReader(bytes.NewBuffer(data), cr.Origin()); err == nil {
cr.nextBatchFn = f cr.nextBatchFn = f
cr.metrics.RecordChannelInputBytes(len(data))
return nil return nil
} else { } else {
cr.log.Error("Error creating batch reader from channel data", "err", err) cr.log.Error("Error creating batch reader from channel data", "err", err)
......
...@@ -206,6 +206,9 @@ func BlockToBatch(block *types.Block) (*BatchData, L1BlockInfo, error) { ...@@ -206,6 +206,9 @@ func BlockToBatch(block *types.Block) (*BatchData, L1BlockInfo, error) {
} }
opaqueTxs = append(opaqueTxs, otx) opaqueTxs = append(opaqueTxs, otx)
} }
if len(block.Transactions()) == 0 {
return nil, L1BlockInfo{}, fmt.Errorf("block %v has no transactions", block.Hash())
}
l1InfoTx := block.Transactions()[0] l1InfoTx := block.Transactions()[0]
if l1InfoTx.Type() != types.DepositTxType { if l1InfoTx.Type() != types.DepositTxType {
return nil, L1BlockInfo{}, ErrNotDepositTx return nil, L1BlockInfo{}, ErrNotDepositTx
......
...@@ -132,3 +132,9 @@ func TestForceCloseTxData(t *testing.T) { ...@@ -132,3 +132,9 @@ func TestForceCloseTxData(t *testing.T) {
} }
} }
} }
func TestBlockToBatchValidity(t *testing.T) {
block := new(types.Block)
_, _, err := BlockToBatch(block)
require.ErrorContains(t, err, "has no transactions")
}
...@@ -17,6 +17,11 @@ import ( ...@@ -17,6 +17,11 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-node/rollup/sync"
) )
type attributesWithParent struct {
attributes *eth.PayloadAttributes
parent eth.L2BlockRef
}
type NextAttributesProvider interface { type NextAttributesProvider interface {
Origin() eth.L1BlockRef Origin() eth.L1BlockRef
NextAttributes(context.Context, eth.L2BlockRef) (*eth.PayloadAttributes, error) NextAttributes(context.Context, eth.L2BlockRef) (*eth.PayloadAttributes, error)
...@@ -76,6 +81,10 @@ const maxUnsafePayloadsMemory = 500 * 1024 * 1024 ...@@ -76,6 +81,10 @@ const maxUnsafePayloadsMemory = 500 * 1024 * 1024
// And then we add 1 to make pruning easier by leaving room for a new item without pruning the 32*4. // And then we add 1 to make pruning easier by leaving room for a new item without pruning the 32*4.
const finalityLookback = 4*32 + 1 const finalityLookback = 4*32 + 1
// finalityDelay is the number of L1 blocks to traverse before trying to finalize L2 blocks again.
// We do not want to do this too often, since it requires fetching a L1 block by number, so no cache data.
const finalityDelay = 64
type FinalityData struct { type FinalityData struct {
// The last L2 block that was fully derived and inserted into the L2 engine while processing this L1 block. // The last L2 block that was fully derived and inserted into the L2 engine while processing this L1 block.
L2Block eth.L2BlockRef L2Block eth.L2BlockRef
...@@ -102,12 +111,16 @@ type EngineQueue struct { ...@@ -102,12 +111,16 @@ type EngineQueue struct {
// This update may repeat if the engine returns a temporary error. // This update may repeat if the engine returns a temporary error.
needForkchoiceUpdate bool needForkchoiceUpdate bool
// finalizedL1 is the currently perceived finalized L1 block.
// This may be ahead of the current traversed origin when syncing.
finalizedL1 eth.L1BlockRef finalizedL1 eth.L1BlockRef
// triedFinalizeAt tracks at which origin we last tried to finalize during sync.
triedFinalizeAt eth.L1BlockRef
// The queued-up attributes // The queued-up attributes
safeAttributesParent eth.L2BlockRef safeAttributes *attributesWithParent
safeAttributes *eth.PayloadAttributes unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates
unsafePayloads *PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps and duplicates
// Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large. // Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large.
finalityData []FinalityData finalityData []FinalityData
...@@ -171,17 +184,23 @@ func (eq *EngineQueue) Finalize(l1Origin eth.L1BlockRef) { ...@@ -171,17 +184,23 @@ func (eq *EngineQueue) Finalize(l1Origin eth.L1BlockRef) {
eq.log.Error("ignoring old L1 finalized block signal! Is the L1 provider corrupted?", "prev_finalized_l1", eq.finalizedL1, "signaled_finalized_l1", l1Origin) eq.log.Error("ignoring old L1 finalized block signal! Is the L1 provider corrupted?", "prev_finalized_l1", eq.finalizedL1, "signaled_finalized_l1", l1Origin)
return return
} }
// Perform a safety check: the L1 finalization signal is only accepted if we previously processed the L1 block.
// This prevents a corrupt L1 provider from tricking us in recognizing a L1 block inconsistent with the L1 chain we are on. // remember the L1 finalization signal
// Missing a finality signal due to empty buffer is fine, it will finalize when the buffer is filled again. eq.finalizedL1 = l1Origin
// Sanity check: we only try to finalize L2 immediately, without fetching additional data,
// if we are on the same chain as the signal.
// If we are on a different chain, the signal will be ignored,
// and tryFinalizeL1Origin() will eventually detect that we are on the wrong chain,
// if not resetting due to reorg elsewhere already.
for _, fd := range eq.finalityData { for _, fd := range eq.finalityData {
if fd.L1Block == l1Origin.ID() { if fd.L1Block == l1Origin.ID() {
eq.finalizedL1 = l1Origin
eq.tryFinalizeL2() eq.tryFinalizeL2()
return return
} }
} }
eq.log.Warn("ignoring finalization signal for unknown L1 block, waiting for new L1 blocks in buffer", "prev_finalized_l1", eq.finalizedL1, "signaled_finalized_l1", l1Origin)
eq.log.Info("received L1 finality signal, but missing data for immediate L2 finalization", "prev_finalized_l1", eq.finalizedL1, "signaled_finalized_l1", l1Origin)
} }
// FinalizedL1 identifies the L1 chain (incl.) that included and/or produced all the finalized L2 blocks. // FinalizedL1 identifies the L1 chain (incl.) that included and/or produced all the finalized L2 blocks.
...@@ -217,14 +236,20 @@ func (eq *EngineQueue) Step(ctx context.Context) error { ...@@ -217,14 +236,20 @@ func (eq *EngineQueue) Step(ctx context.Context) error {
} }
eq.origin = newOrigin eq.origin = newOrigin
eq.postProcessSafeL2() // make sure we track the last L2 safe head for every new L1 block eq.postProcessSafeL2() // make sure we track the last L2 safe head for every new L1 block
// try to finalize the L2 blocks we have synced so far (no-op if L1 finality is behind)
if err := eq.tryFinalizePastL2Blocks(ctx); err != nil {
return err
}
if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF { if next, err := eq.prev.NextAttributes(ctx, eq.safeHead); err == io.EOF {
outOfData = true outOfData = true
} else if err != nil { } else if err != nil {
return err return err
} else { } else {
eq.safeAttributes = next eq.safeAttributes = &attributesWithParent{
eq.safeAttributesParent = eq.safeHead attributes: next,
eq.log.Debug("Adding next safe attributes", "safe_head", eq.safeHead, "next", eq.safeAttributes) parent: eq.safeHead,
}
eq.log.Debug("Adding next safe attributes", "safe_head", eq.safeHead, "next", next)
return NotEnoughData return NotEnoughData
} }
...@@ -271,6 +296,38 @@ func (eq *EngineQueue) verifyNewL1Origin(ctx context.Context, newOrigin eth.L1Bl ...@@ -271,6 +296,38 @@ func (eq *EngineQueue) verifyNewL1Origin(ctx context.Context, newOrigin eth.L1Bl
return nil return nil
} }
func (eq *EngineQueue) tryFinalizePastL2Blocks(ctx context.Context) error {
if eq.finalizedL1 == (eth.L1BlockRef{}) {
return nil
}
// If the L1 is finalized beyond the point we are traversing (e.g. during sync),
// then we should check if we can finalize this L1 block we are traversing.
// Otherwise, nothing to act on here, we will finalize later on a new finality signal matching the recent history.
if eq.finalizedL1.Number < eq.origin.Number {
return nil
}
// If we recently tried finalizing, then don't try again just yet, but traverse more of L1 first.
if eq.triedFinalizeAt != (eth.L1BlockRef{}) && eq.origin.Number <= eq.triedFinalizeAt.Number+finalityDelay {
return nil
}
eq.log.Info("processing L1 finality information", "l1_finalized", eq.finalizedL1, "l1_origin", eq.origin, "previous", eq.triedFinalizeAt)
// Sanity check we are indeed on the finalizing chain, and not stuck on something else.
// We assume that the block-by-number query is consistent with the previously received finalized chain signal
ref, err := eq.l1Fetcher.L1BlockRefByNumber(ctx, eq.origin.Number)
if err != nil {
return NewTemporaryError(fmt.Errorf("failed to check if on finalizing L1 chain: %w", err))
}
if ref.Hash != eq.origin.Hash {
return NewResetError(fmt.Errorf("need to reset, we are on %s, not on the finalizing L1 chain %s (towards %s)", eq.origin, ref, eq.finalizedL1))
}
eq.tryFinalizeL2()
return nil
}
// tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized, // tryFinalizeL2 traverses the past L1 blocks, checks if any has been finalized,
// and then marks the latest fully derived L2 block from this as finalized, // and then marks the latest fully derived L2 block from this as finalized,
// or defaults to the current finalized L2 block. // or defaults to the current finalized L2 block.
...@@ -278,6 +335,7 @@ func (eq *EngineQueue) tryFinalizeL2() { ...@@ -278,6 +335,7 @@ func (eq *EngineQueue) tryFinalizeL2() {
if eq.finalizedL1 == (eth.L1BlockRef{}) { if eq.finalizedL1 == (eth.L1BlockRef{}) {
return // if no L1 information is finalized yet, then skip this return // if no L1 information is finalized yet, then skip this
} }
eq.triedFinalizeAt = eq.origin
// default to keep the same finalized block // default to keep the same finalized block
finalizedL2 := eq.finalized finalizedL2 := eq.finalized
// go through the latest inclusion data, and find the last L2 block that was derived from a finalized L1 block // go through the latest inclusion data, and find the last L2 block that was derived from a finalized L1 block
...@@ -430,15 +488,19 @@ func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error { ...@@ -430,15 +488,19 @@ func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error {
return nil return nil
} }
// validate the safe attributes before processing them. The engine may have completed processing them through other means. // validate the safe attributes before processing them. The engine may have completed processing them through other means.
if eq.safeHead != eq.safeAttributesParent { if eq.safeHead != eq.safeAttributes.parent {
if eq.safeHead.ParentHash != eq.safeAttributesParent.Hash { // Previously the attribute's parent was the safe head. If the safe head advances so safe head's parent is the same as the
return NewResetError(fmt.Errorf("safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s", // attribute's parent then we need to cancel the attributes.
eq.safeHead, eq.safeHead.ParentID(), eq.safeAttributesParent)) if eq.safeHead.ParentHash == eq.safeAttributes.parent.Hash {
eq.log.Warn("queued safe attributes are stale, safehead progressed",
"safe_head", eq.safeHead, "safe_head_parent", eq.safeHead.ParentID(), "attributes_parent", eq.safeAttributes.parent)
eq.safeAttributes = nil
return nil
} }
eq.log.Warn("queued safe attributes are stale, safe-head progressed", // If something other than a simple advance occurred, perform a full reset
"safe_head", eq.safeHead, "safe_head_parent", eq.safeHead.ParentID(), "attributes_parent", eq.safeAttributesParent) return NewResetError(fmt.Errorf("safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s",
eq.safeAttributes = nil eq.safeHead, eq.safeHead.ParentID(), eq.safeAttributes.parent))
return nil
} }
if eq.safeHead.Number < eq.unsafeHead.Number { if eq.safeHead.Number < eq.unsafeHead.Number {
return eq.consolidateNextSafeAttributes(ctx) return eq.consolidateNextSafeAttributes(ctx)
...@@ -468,7 +530,7 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error ...@@ -468,7 +530,7 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error
} }
return NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err)) return NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err))
} }
if err := AttributesMatchBlock(eq.safeAttributes, eq.safeHead.Hash, payload, eq.log); err != nil { if err := AttributesMatchBlock(eq.safeAttributes.attributes, eq.safeHead.Hash, payload, eq.log); err != nil {
eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.unsafeHead, "safe", eq.safeHead) eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.unsafeHead, "safe", eq.safeHead)
// geth cannot wind back a chain without reorging to a new, previously non-canonical, block // geth cannot wind back a chain without reorging to a new, previously non-canonical, block
return eq.forceNextSafeAttributes(ctx) return eq.forceNextSafeAttributes(ctx)
...@@ -493,7 +555,7 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error { ...@@ -493,7 +555,7 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error {
if eq.safeAttributes == nil { if eq.safeAttributes == nil {
return nil return nil
} }
attrs := eq.safeAttributes attrs := eq.safeAttributes.attributes
errType, err := eq.StartPayload(ctx, eq.safeHead, attrs, true) errType, err := eq.StartPayload(ctx, eq.safeHead, attrs, true)
if err == nil { if err == nil {
_, errType, err = eq.ConfirmPayload(ctx) _, errType, err = eq.ConfirmPayload(ctx)
...@@ -664,10 +726,12 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System ...@@ -664,10 +726,12 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System
eq.log.Debug("Reset engine queue", "safeHead", safe, "unsafe", unsafe, "safe_timestamp", safe.Time, "unsafe_timestamp", unsafe.Time, "l1Origin", l1Origin) eq.log.Debug("Reset engine queue", "safeHead", safe, "unsafe", unsafe, "safe_timestamp", safe.Time, "unsafe_timestamp", unsafe.Time, "l1Origin", l1Origin)
eq.unsafeHead = unsafe eq.unsafeHead = unsafe
eq.safeHead = safe eq.safeHead = safe
eq.safeAttributes = nil
eq.finalized = finalized eq.finalized = finalized
eq.resetBuildingState() eq.resetBuildingState()
eq.needForkchoiceUpdate = true eq.needForkchoiceUpdate = true
eq.finalityData = eq.finalityData[:0] eq.finalityData = eq.finalityData[:0]
// note: finalizedL1 and triedFinalizeAt do not reset, since these do not change between reorgs.
// note: we do not clear the unsafe payloads queue; if the payloads are not applicable anymore the parent hash checks will clear out the old payloads. // note: we do not clear the unsafe payloads queue; if the payloads are not applicable anymore the parent hash checks will clear out the old payloads.
eq.origin = pipelineOrigin eq.origin = pipelineOrigin
eq.sysCfg = l1Cfg eq.sysCfg = l1Cfg
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum-optimism/optimism/op-node/testutils"
...@@ -1007,3 +1008,102 @@ func TestBlockBuildingRace(t *testing.T) { ...@@ -1007,3 +1008,102 @@ func TestBlockBuildingRace(t *testing.T) {
l1F.AssertExpectations(t) l1F.AssertExpectations(t)
eng.AssertExpectations(t) eng.AssertExpectations(t)
} }
func TestResetLoop(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
eng := &testutils.MockEngine{}
l1F := &testutils.MockL1Source{}
rng := rand.New(rand.NewSource(1234))
refA := testutils.RandomBlockRef(rng)
refA0 := eth.L2BlockRef{
Hash: testutils.RandomHash(rng),
Number: 0,
ParentHash: common.Hash{},
Time: refA.Time,
L1Origin: refA.ID(),
SequenceNumber: 0,
}
gasLimit := eth.Uint64Quantity(20_000_000)
cfg := &rollup.Config{
Genesis: rollup.Genesis{
L1: refA.ID(),
L2: refA0.ID(),
L2Time: refA0.Time,
SystemConfig: eth.SystemConfig{
BatcherAddr: common.Address{42},
Overhead: [32]byte{123},
Scalar: [32]byte{42},
GasLimit: 20_000_000,
},
},
BlockTime: 1,
SeqWindowSize: 2,
}
refA1 := eth.L2BlockRef{
Hash: testutils.RandomHash(rng),
Number: refA0.Number + 1,
ParentHash: refA0.Hash,
Time: refA0.Time + cfg.BlockTime,
L1Origin: refA.ID(),
SequenceNumber: 1,
}
refA2 := eth.L2BlockRef{
Hash: testutils.RandomHash(rng),
Number: refA1.Number + 1,
ParentHash: refA1.Hash,
Time: refA1.Time + cfg.BlockTime,
L1Origin: refA.ID(),
SequenceNumber: 2,
}
attrs := &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(refA2.Time),
PrevRandao: eth.Bytes32{},
SuggestedFeeRecipient: common.Address{},
Transactions: nil,
NoTxPool: false,
GasLimit: &gasLimit,
}
eng.ExpectL2BlockRefByLabel(eth.Finalized, refA0, nil)
eng.ExpectL2BlockRefByLabel(eth.Safe, refA1, nil)
eng.ExpectL2BlockRefByLabel(eth.Unsafe, refA2, nil)
eng.ExpectL2BlockRefByHash(refA1.Hash, refA1, nil)
eng.ExpectL2BlockRefByHash(refA0.Hash, refA0, nil)
eng.ExpectSystemConfigByL2Hash(refA0.Hash, cfg.Genesis.SystemConfig, nil)
l1F.ExpectL1BlockRefByNumber(refA.Number, refA, nil)
l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil)
l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil)
prev := &fakeAttributesQueue{origin: refA, attrs: attrs}
eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F)
eq.unsafeHead = refA2
eq.safeHead = refA1
eq.finalized = refA0
// Qeueue up the safe attributes
require.Nil(t, eq.safeAttributes)
require.ErrorIs(t, eq.Step(context.Background()), NotEnoughData)
require.NotNil(t, eq.safeAttributes)
// Peform the reset
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
// Expect a FCU after the reset
preFc := &eth.ForkchoiceState{
HeadBlockHash: refA2.Hash,
SafeBlockHash: refA0.Hash,
FinalizedBlockHash: refA0.Hash,
}
eng.ExpectForkchoiceUpdate(preFc, nil, nil, nil)
require.NoError(t, eq.Step(context.Background()), "clean forkchoice state after reset")
// Crux of the test. Should be in a valid state after the reset.
require.ErrorIs(t, eq.Step(context.Background()), NotEnoughData, "Should be able to step after a reset")
l1F.AssertExpectations(t)
eng.AssertExpectations(t)
}
...@@ -2,7 +2,7 @@ package derive ...@@ -2,7 +2,7 @@ package derive
import ( import (
"bytes" "bytes"
"encoding/binary" "errors"
"fmt" "fmt"
"math/big" "math/big"
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-service/solabi"
) )
const ( const (
...@@ -45,68 +46,90 @@ type L1BlockInfo struct { ...@@ -45,68 +46,90 @@ type L1BlockInfo struct {
L1FeeScalar eth.Bytes32 L1FeeScalar eth.Bytes32
} }
// Binary Format
// +---------+--------------------------+
// | Bytes | Field |
// +---------+--------------------------+
// | 4 | Function signature |
// | 32 | Number |
// | 32 | Time |
// | 32 | BaseFee |
// | 32 | BlockHash |
// | 32 | SequenceNumber |
// | 32 | BatcherAddr |
// | 32 | L1FeeOverhead |
// | 32 | L1FeeScalar |
// +---------+--------------------------+
func (info *L1BlockInfo) MarshalBinary() ([]byte, error) { func (info *L1BlockInfo) MarshalBinary() ([]byte, error) {
data := make([]byte, L1InfoLen) w := bytes.NewBuffer(make([]byte, 0, L1InfoLen))
offset := 0 if err := solabi.WriteSignature(w, L1InfoFuncBytes4); err != nil {
copy(data[offset:4], L1InfoFuncBytes4) return nil, err
offset += 4 }
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Number) if err := solabi.WriteUint64(w, info.Number); err != nil {
offset += 32 return nil, err
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Time) }
offset += 32 if err := solabi.WriteUint64(w, info.Time); err != nil {
// Ensure that the baseFee is not too large. return nil, err
if info.BaseFee.BitLen() > 256 { }
return nil, fmt.Errorf("base fee exceeds 256 bits: %d", info.BaseFee) if err := solabi.WriteUint256(w, info.BaseFee); err != nil {
} return nil, err
info.BaseFee.FillBytes(data[offset : offset+32]) }
offset += 32 if err := solabi.WriteHash(w, info.BlockHash); err != nil {
copy(data[offset:offset+32], info.BlockHash.Bytes()) return nil, err
offset += 32 }
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.SequenceNumber) if err := solabi.WriteUint64(w, info.SequenceNumber); err != nil {
offset += 32 return nil, err
copy(data[offset+12:offset+32], info.BatcherAddr[:]) }
offset += 32 if err := solabi.WriteAddress(w, info.BatcherAddr); err != nil {
copy(data[offset:offset+32], info.L1FeeOverhead[:]) return nil, err
offset += 32 }
copy(data[offset:offset+32], info.L1FeeScalar[:]) if err := solabi.WriteEthBytes32(w, info.L1FeeOverhead); err != nil {
return data, nil return nil, err
}
if err := solabi.WriteEthBytes32(w, info.L1FeeScalar); err != nil {
return nil, err
}
return w.Bytes(), nil
} }
func (info *L1BlockInfo) UnmarshalBinary(data []byte) error { func (info *L1BlockInfo) UnmarshalBinary(data []byte) error {
if len(data) != L1InfoLen { if len(data) != L1InfoLen {
return fmt.Errorf("data is unexpected length: %d", len(data)) return fmt.Errorf("data is unexpected length: %d", len(data))
} }
var padding [24]byte reader := bytes.NewReader(data)
offset := 4
if !bytes.Equal(data[0:offset], L1InfoFuncBytes4) { var err error
return fmt.Errorf("data does not match L1 info function signature: 0x%x", data[offset:4]) if _, err := solabi.ReadAndValidateSignature(reader, L1InfoFuncBytes4); err != nil {
return err
}
if info.Number, err = solabi.ReadUint64(reader); err != nil {
return err
}
if info.Time, err = solabi.ReadUint64(reader); err != nil {
return err
}
if info.BaseFee, err = solabi.ReadUint256(reader); err != nil {
return err
}
if info.BlockHash, err = solabi.ReadHash(reader); err != nil {
return err
}
if info.SequenceNumber, err = solabi.ReadUint64(reader); err != nil {
return err
}
if info.BatcherAddr, err = solabi.ReadAddress(reader); err != nil {
return err
}
if info.L1FeeOverhead, err = solabi.ReadEthBytes32(reader); err != nil {
return err
}
if info.L1FeeScalar, err = solabi.ReadEthBytes32(reader); err != nil {
return err
}
if !solabi.EmptyReader(reader) {
return errors.New("too many bytes")
} }
info.Number = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 info number exceeds uint64 bounds: %x", data[offset:offset+32])
}
offset += 32
info.Time = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 info time exceeds uint64 bounds: %x", data[offset:offset+32])
}
offset += 32
info.BaseFee = new(big.Int).SetBytes(data[offset : offset+32])
offset += 32
info.BlockHash.SetBytes(data[offset : offset+32])
offset += 32
info.SequenceNumber = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 info sequence number exceeds uint64 bounds: %x", data[offset:offset+32])
}
offset += 32
info.BatcherAddr.SetBytes(data[offset+12 : offset+32])
offset += 32
copy(info.L1FeeOverhead[:], data[offset:offset+32])
offset += 32
copy(info.L1FeeScalar[:], data[offset:offset+32])
return nil return nil
} }
......
...@@ -86,8 +86,6 @@ func (l1t *L1Traversal) AdvanceL1Block(ctx context.Context) error { ...@@ -86,8 +86,6 @@ func (l1t *L1Traversal) AdvanceL1Block(ctx context.Context) error {
} }
// Reset sets the internal L1 block to the supplied base. // Reset sets the internal L1 block to the supplied base.
// Note that the next call to `NextL1Block` will return the block after `base`
// TODO: Walk one back/figure this out.
func (l1t *L1Traversal) Reset(ctx context.Context, base eth.L1BlockRef, cfg eth.SystemConfig) error { func (l1t *L1Traversal) Reset(ctx context.Context, base eth.L1BlockRef, cfg eth.SystemConfig) error {
l1t.block = base l1t.block = base
l1t.done = false l1t.done = false
......
...@@ -15,6 +15,7 @@ type Metrics interface { ...@@ -15,6 +15,7 @@ type Metrics interface {
RecordL1Ref(name string, ref eth.L1BlockRef) RecordL1Ref(name string, ref eth.L1BlockRef)
RecordL2Ref(name string, ref eth.L2BlockRef) RecordL2Ref(name string, ref eth.L2BlockRef)
RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID)
RecordChannelInputBytes(inputCompresedBytes int)
} }
type L1Fetcher interface { type L1Fetcher interface {
...@@ -82,7 +83,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch ...@@ -82,7 +83,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch
l1Src := NewL1Retrieval(log, dataSrc, l1Traversal) l1Src := NewL1Retrieval(log, dataSrc, l1Traversal)
frameQueue := NewFrameQueue(log, l1Src) frameQueue := NewFrameQueue(log, l1Src)
bank := NewChannelBank(log, cfg, frameQueue, l1Fetcher) bank := NewChannelBank(log, cfg, frameQueue, l1Fetcher)
chInReader := NewChannelInReader(log, bank) chInReader := NewChannelInReader(log, bank, metrics)
batchQueue := NewBatchQueue(log, cfg, chInReader) batchQueue := NewBatchQueue(log, cfg, chInReader)
attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, engine) attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, engine)
attributesQueue := NewAttributesQueue(log, cfg, attrBuilder, batchQueue) attributesQueue := NewAttributesQueue(log, cfg, attrBuilder, batchQueue)
......
...@@ -2,7 +2,7 @@ package derive ...@@ -2,7 +2,7 @@ package derive
import ( import (
"bytes" "bytes"
"encoding/binary" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/solabi"
) )
var ( var (
...@@ -27,17 +28,6 @@ var ( ...@@ -27,17 +28,6 @@ var (
ConfigUpdateEventVersion0 = common.Hash{} ConfigUpdateEventVersion0 = common.Hash{}
) )
var (
// A left-padded uint256 equal to 32.
oneWordUint = common.Hash{31: 32}
// A left-padded uint256 equal to 64.
twoWordUint = common.Hash{31: 64}
// 24 zero bytes (the padding for a uint64 in a 32 byte word)
uint64Padding = make([]byte, 24)
// 12 zero bytes (the padding for an Ethereum address in a 32 byte word)
addressPadding = make([]byte, 12)
)
// UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg // UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg
func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config) error { func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config) error {
var result error var result error
...@@ -84,90 +74,60 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L ...@@ -84,90 +74,60 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
// Create a reader of the unindexed data // Create a reader of the unindexed data
reader := bytes.NewReader(ev.Data) reader := bytes.NewReader(ev.Data)
// Counter for the number of bytes read from `reader` via `readWord`
countReadBytes := 0
// Helper function to read a word from the log data reader
readWord := func() (b [32]byte) {
if _, err := reader.Read(b[:]); err != nil {
// If there is an error reading the next 32 bytes from the reader, return an empty
// 32 byte array. We always check that the number of bytes read (`countReadBytes`)
// is equal to the expected amount at the end of each switch case.
return b
}
countReadBytes += 32
return b
}
// Attempt to read unindexed data // Attempt to read unindexed data
switch updateType { switch updateType {
case SystemConfigUpdateBatcher: case SystemConfigUpdateBatcher:
// Read the pointer, it should always equal 32. if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
if word := readWord(); word != oneWordUint { return NewCriticalError(errors.New("invalid pointer field"))
return fmt.Errorf("expected offset to point to length location, but got %s", word)
} }
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
// Read the length, it should also always equal 32. return NewCriticalError(errors.New("invalid length field"))
if word := readWord(); word != oneWordUint {
return fmt.Errorf("expected length to be 32 bytes, but got %s", word)
} }
address, err := solabi.ReadAddress(reader)
// Indexing `word` directly is always safe here, it is guaranteed to be 32 bytes in length. if err != nil {
// Check that the batcher address is correctly zero-padded. return NewCriticalError(errors.New("could not read address"))
word := readWord()
if !bytes.Equal(word[:12], addressPadding) {
return fmt.Errorf("expected version 0 batcher hash with zero padding, but got %x", word)
} }
destSysCfg.BatcherAddr.SetBytes(word[12:]) if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
if countReadBytes != 32*3 {
return NewCriticalError(fmt.Errorf("expected 32*3 bytes in batcher hash update, but got %d bytes", len(ev.Data)))
} }
destSysCfg.BatcherAddr = address
return nil return nil
case SystemConfigUpdateGasConfig: case SystemConfigUpdateGasConfig:
// Read the pointer, it should always equal 32. if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
if word := readWord(); word != oneWordUint { return NewCriticalError(errors.New("invalid pointer field"))
return fmt.Errorf("expected offset to point to length location, but got %s", word)
} }
if length, err := solabi.ReadUint64(reader); err != nil || length != 64 {
// Read the length, it should always equal 64. return NewCriticalError(errors.New("invalid length field"))
if word := readWord(); word != twoWordUint {
return fmt.Errorf("expected length to be 64 bytes, but got %s", word)
} }
overhead, err := solabi.ReadEthBytes32(reader)
// Set the system config's overhead and scalar values to the values read from the log if err != nil {
destSysCfg.Overhead = readWord() return NewCriticalError(errors.New("could not read overhead"))
destSysCfg.Scalar = readWord()
if countReadBytes != 32*4 {
return NewCriticalError(fmt.Errorf("expected 32*4 bytes in GPO params update data, but got %d", len(ev.Data)))
} }
scalar, err := solabi.ReadEthBytes32(reader)
if err != nil {
return NewCriticalError(errors.New("could not read scalar"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
}
destSysCfg.Overhead = overhead
destSysCfg.Scalar = scalar
return nil return nil
case SystemConfigUpdateGasLimit: case SystemConfigUpdateGasLimit:
// Read the pointer, it should always equal 32. if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
if word := readWord(); word != oneWordUint { return NewCriticalError(errors.New("invalid pointer field"))
return fmt.Errorf("expected offset to point to length location, but got %s", word)
} }
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
// Read the length, it should also always equal 32. return NewCriticalError(errors.New("invalid length field"))
if word := readWord(); word != oneWordUint {
return fmt.Errorf("expected length to be 32 bytes, but got %s", word)
} }
gasLimit, err := solabi.ReadUint64(reader)
// Indexing `word` directly is always safe here, it is guaranteed to be 32 bytes in length. if err != nil {
// Check that the gas limit is correctly zero-padded. return NewCriticalError(errors.New("could not read gas limit"))
word := readWord()
if !bytes.Equal(word[:24], uint64Padding) {
return fmt.Errorf("expected zero padding for gaslimit, but got %x", word)
} }
destSysCfg.GasLimit = binary.BigEndian.Uint64(word[24:]) if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
if countReadBytes != 32*3 {
return NewCriticalError(fmt.Errorf("expected 32*3 bytes in gas limit update, but got %d bytes", len(ev.Data)))
} }
destSysCfg.GasLimit = gasLimit
return nil return nil
case SystemConfigUpdateUnsafeBlockSigner: case SystemConfigUpdateUnsafeBlockSigner:
// Ignored in derivation. This configurable applies to runtime configuration outside of the derivation. // Ignored in derivation. This configurable applies to runtime configuration outside of the derivation.
......
...@@ -15,7 +15,7 @@ import ( ...@@ -15,7 +15,7 @@ import (
func RandomL2Block(rng *rand.Rand, txCount int) (*types.Block, []*types.Receipt) { func RandomL2Block(rng *rand.Rand, txCount int) (*types.Block, []*types.Receipt) {
l1Block := types.NewBlock(testutils.RandomHeader(rng), l1Block := types.NewBlock(testutils.RandomHeader(rng),
nil, nil, nil, trie.NewStackTrie(nil)) nil, nil, nil, trie.NewStackTrie(nil))
l1InfoTx, err := derive.L1InfoDeposit(0, l1Block, eth.SystemConfig{}, testutils.RandomBool(rng)) l1InfoTx, err := derive.L1InfoDeposit(0, eth.BlockToInfo(l1Block), eth.SystemConfig{}, testutils.RandomBool(rng))
if err != nil { if err != nil {
panic("L1InfoDeposit: " + err.Error()) panic("L1InfoDeposit: " + err.Error())
} }
......
...@@ -21,6 +21,7 @@ type Metrics interface { ...@@ -21,6 +21,7 @@ type Metrics interface {
RecordL1Ref(name string, ref eth.L1BlockRef) RecordL1Ref(name string, ref eth.L1BlockRef)
RecordL2Ref(name string, ref eth.L2BlockRef) RecordL2Ref(name string, ref eth.L2BlockRef)
RecordChannelInputBytes(inputCompresedBytes int)
RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID)
......
...@@ -24,3 +24,13 @@ func ComputeL2OutputRoot(proofElements *bindings.TypesOutputRootProof) (eth.Byte ...@@ -24,3 +24,13 @@ func ComputeL2OutputRoot(proofElements *bindings.TypesOutputRootProof) (eth.Byte
) )
return eth.Bytes32(digest), nil return eth.Bytes32(digest), nil
} }
func ComputeL2OutputRootV0(block eth.BlockInfo, storageRoot [32]byte) (eth.Bytes32, error) {
var l2OutputRootVersion eth.Bytes32 // it's zero for now
return ComputeL2OutputRoot(&bindings.TypesOutputRootProof{
Version: l2OutputRootVersion,
StateRoot: block.Root(),
MessagePasserStorageRoot: storageRoot,
LatestBlockhash: block.Hash(),
})
}
...@@ -110,7 +110,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -110,7 +110,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
} }
lgr.Info("Loaded current L2 heads", "unsafe", result.Unsafe, "safe", result.Safe, "finalized", result.Finalized, lgr.Info("Loaded current L2 heads", "unsafe", result.Unsafe, "safe", result.Safe, "finalized", result.Finalized,
"unsafe_origin", result.Unsafe.L1Origin, "unsafe_origin", result.Safe.L1Origin) "unsafe_origin", result.Unsafe.L1Origin, "safe_origin", result.Safe.L1Origin)
// Remember original unsafe block to determine reorg depth // Remember original unsafe block to determine reorg depth
prevUnsafe := result.Unsafe prevUnsafe := result.Unsafe
...@@ -207,7 +207,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -207,7 +207,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
// Don't traverse further than the finalized head to find a safe head // Don't traverse further than the finalized head to find a safe head
if n.Number == result.Finalized.Number { if n.Number == result.Finalized.Number {
lgr.Info("Hit finalized L2 head, returning immediately", "unsafe", result.Unsafe, "safe", result.Safe, lgr.Info("Hit finalized L2 head, returning immediately", "unsafe", result.Unsafe, "safe", result.Safe,
"finalized", result.Finalized, "unsafe_origin", result.Unsafe.L1Origin, "unsafe_origin", result.Safe.L1Origin) "finalized", result.Finalized, "unsafe_origin", result.Unsafe.L1Origin, "safe_origin", result.Safe.L1Origin)
result.Safe = n result.Safe = n
return result, nil return result, nil
} }
......
...@@ -24,6 +24,7 @@ type IterativeBatchCall[K any, V any] struct { ...@@ -24,6 +24,7 @@ type IterativeBatchCall[K any, V any] struct {
makeRequest func(K) (V, rpc.BatchElem) makeRequest func(K) (V, rpc.BatchElem)
getBatch BatchCallContextFn getBatch BatchCallContextFn
getSingle CallContextFn
requestsValues []V requestsValues []V
scheduled chan rpc.BatchElem scheduled chan rpc.BatchElem
...@@ -35,6 +36,7 @@ func NewIterativeBatchCall[K any, V any]( ...@@ -35,6 +36,7 @@ func NewIterativeBatchCall[K any, V any](
requestsKeys []K, requestsKeys []K,
makeRequest func(K) (V, rpc.BatchElem), makeRequest func(K) (V, rpc.BatchElem),
getBatch BatchCallContextFn, getBatch BatchCallContextFn,
getSingle CallContextFn,
batchSize int) *IterativeBatchCall[K, V] { batchSize int) *IterativeBatchCall[K, V] {
if len(requestsKeys) < batchSize { if len(requestsKeys) < batchSize {
...@@ -47,6 +49,7 @@ func NewIterativeBatchCall[K any, V any]( ...@@ -47,6 +49,7 @@ func NewIterativeBatchCall[K any, V any](
out := &IterativeBatchCall[K, V]{ out := &IterativeBatchCall[K, V]{
completed: 0, completed: 0,
getBatch: getBatch, getBatch: getBatch,
getSingle: getSingle,
requestsKeys: requestsKeys, requestsKeys: requestsKeys,
batchSize: batchSize, batchSize: batchSize,
makeRequest: makeRequest, makeRequest: makeRequest,
...@@ -84,6 +87,11 @@ func (ibc *IterativeBatchCall[K, V]) Fetch(ctx context.Context) error { ...@@ -84,6 +87,11 @@ func (ibc *IterativeBatchCall[K, V]) Fetch(ctx context.Context) error {
ibc.resetLock.RLock() ibc.resetLock.RLock()
defer ibc.resetLock.RUnlock() defer ibc.resetLock.RUnlock()
// return early if context is Done
if ctx.Err() != nil {
return ctx.Err()
}
// collect a batch from the requests channel // collect a batch from the requests channel
batch := make([]rpc.BatchElem, 0, ibc.batchSize) batch := make([]rpc.BatchElem, 0, ibc.batchSize)
// wait for first element // wait for first element
...@@ -119,11 +127,23 @@ func (ibc *IterativeBatchCall[K, V]) Fetch(ctx context.Context) error { ...@@ -119,11 +127,23 @@ func (ibc *IterativeBatchCall[K, V]) Fetch(ctx context.Context) error {
break break
} }
if err := ibc.getBatch(ctx, batch); err != nil { if len(batch) == 0 {
for _, r := range batch { return nil
ibc.scheduled <- r }
if ibc.batchSize == 1 {
first := batch[0]
if err := ibc.getSingle(ctx, &first.Result, first.Method, first.Args...); err != nil {
ibc.scheduled <- first
return err
}
} else {
if err := ibc.getBatch(ctx, batch); err != nil {
for _, r := range batch {
ibc.scheduled <- r
}
return fmt.Errorf("failed batch-retrieval: %w", err)
} }
return fmt.Errorf("failed batch-retrieval: %w", err)
} }
var result error var result error
for _, elem := range batch { for _, elem := range batch {
......
...@@ -34,7 +34,8 @@ type batchTestCase struct { ...@@ -34,7 +34,8 @@ type batchTestCase struct {
batchSize int batchSize int
batchCalls []batchCall batchCalls []batchCall
singleCalls []elemCall
mock.Mock mock.Mock
} }
...@@ -53,7 +54,14 @@ func (tc *batchTestCase) GetBatch(ctx context.Context, b []rpc.BatchElem) error ...@@ -53,7 +54,14 @@ func (tc *batchTestCase) GetBatch(ctx context.Context, b []rpc.BatchElem) error
if ctx.Err() != nil { if ctx.Err() != nil {
return ctx.Err() return ctx.Err()
} }
return tc.Mock.MethodCalled("get", b).Get(0).([]error)[0] return tc.Mock.MethodCalled("getBatch", b).Get(0).([]error)[0]
}
func (tc *batchTestCase) GetSingle(ctx context.Context, result any, method string, args ...any) error {
if ctx.Err() != nil {
return ctx.Err()
}
return tc.Mock.MethodCalled("getSingle", (*(result.(*interface{}))).(*string), method, args[0]).Get(0).([]error)[0]
} }
var mockErr = errors.New("mockErr") var mockErr = errors.New("mockErr")
...@@ -64,7 +72,7 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -64,7 +72,7 @@ func (tc *batchTestCase) Run(t *testing.T) {
keys[i] = i keys[i] = i
} }
makeMock := func(bci int, bc batchCall) func(args mock.Arguments) { makeBatchMock := func(bc batchCall) func(args mock.Arguments) {
return func(args mock.Arguments) { return func(args mock.Arguments) {
batch := args[0].([]rpc.BatchElem) batch := args[0].([]rpc.BatchElem)
for i, elem := range batch { for i, elem := range batch {
...@@ -83,7 +91,7 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -83,7 +91,7 @@ func (tc *batchTestCase) Run(t *testing.T) {
} }
} }
// mock all the results of the batch calls // mock all the results of the batch calls
for bci, bc := range tc.batchCalls { for _, bc := range tc.batchCalls {
var batch []rpc.BatchElem var batch []rpc.BatchElem
for _, elem := range bc.elems { for _, elem := range bc.elems {
batch = append(batch, rpc.BatchElem{ batch = append(batch, rpc.BatchElem{
...@@ -94,10 +102,30 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -94,10 +102,30 @@ func (tc *batchTestCase) Run(t *testing.T) {
}) })
} }
if len(bc.elems) > 0 { if len(bc.elems) > 0 {
tc.On("get", batch).Once().Run(makeMock(bci, bc)).Return([]error{bc.rpcErr}) // wrap to preserve nil as type of error tc.On("getBatch", batch).Once().Run(makeBatchMock(bc)).Return([]error{bc.rpcErr}) // wrap to preserve nil as type of error
}
}
makeSingleMock := func(ec elemCall) func(args mock.Arguments) {
return func(args mock.Arguments) {
result := args[0].(*string)
id := args[2].(int)
require.Equal(t, ec.id, id, "element should match expected element")
if ec.err {
*result = ""
} else {
*result = fmt.Sprintf("mock result id %d", id)
}
} }
} }
iter := NewIterativeBatchCall[int, *string](keys, makeTestRequest, tc.GetBatch, tc.batchSize) // mock the results of unbatched calls
for _, ec := range tc.singleCalls {
var ret error
if ec.err {
ret = mockErr
}
tc.On("getSingle", new(string), "testing_foobar", ec.id).Once().Run(makeSingleMock(ec)).Return([]error{ret})
}
iter := NewIterativeBatchCall[int, *string](keys, makeTestRequest, tc.GetBatch, tc.GetSingle, tc.batchSize)
for i, bc := range tc.batchCalls { for i, bc := range tc.batchCalls {
ctx := context.Background() ctx := context.Background()
if bc.makeCtx != nil { if bc.makeCtx != nil {
...@@ -116,6 +144,20 @@ func (tc *batchTestCase) Run(t *testing.T) { ...@@ -116,6 +144,20 @@ func (tc *batchTestCase) Run(t *testing.T) {
} }
} }
} }
for i, ec := range tc.singleCalls {
ctx := context.Background()
err := iter.Fetch(ctx)
if err == io.EOF {
require.Equal(t, i, len(tc.singleCalls)-1, "EOF only on last call")
} else {
require.False(t, iter.Complete())
if ec.err {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
require.True(t, iter.Complete(), "batch iter should be complete after the expected calls") require.True(t, iter.Complete(), "batch iter should be complete after the expected calls")
out, err := iter.Result() out, err := iter.Result()
require.NoError(t, err) require.NoError(t, err)
...@@ -154,6 +196,37 @@ func TestFetchBatched(t *testing.T) { ...@@ -154,6 +196,37 @@ func TestFetchBatched(t *testing.T) {
}, },
}, },
}, },
{
name: "single element",
items: 1,
batchSize: 4,
singleCalls: []elemCall{
{id: 0, err: false},
},
},
{
name: "unbatched",
items: 4,
batchSize: 1,
singleCalls: []elemCall{
{id: 0, err: false},
{id: 1, err: false},
{id: 2, err: false},
{id: 3, err: false},
},
},
{
name: "unbatched with retry",
items: 4,
batchSize: 1,
singleCalls: []elemCall{
{id: 0, err: false},
{id: 1, err: true},
{id: 2, err: false},
{id: 3, err: false},
{id: 1, err: false},
},
},
{ {
name: "split", name: "split",
items: 5, items: 5,
...@@ -240,7 +313,7 @@ func TestFetchBatched(t *testing.T) { ...@@ -240,7 +313,7 @@ func TestFetchBatched(t *testing.T) {
}, },
{ {
name: "context timeout", name: "context timeout",
items: 1, items: 2,
batchSize: 3, batchSize: 3,
batchCalls: []batchCall{ batchCalls: []batchCall{
{ {
...@@ -255,6 +328,7 @@ func TestFetchBatched(t *testing.T) { ...@@ -255,6 +328,7 @@ func TestFetchBatched(t *testing.T) {
{ {
elems: []elemCall{ elems: []elemCall{
{id: 0, err: false}, {id: 0, err: false},
{id: 1, err: false},
}, },
err: "", err: "",
}, },
......
package sources
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
)
type DebugClient struct {
callContext CallContextFn
}
func NewDebugClient(callContext CallContextFn) *DebugClient {
return &DebugClient{callContext}
}
func (o *DebugClient) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
// MPT nodes are stored as the hash of the node (with no prefix)
node, err := o.dbGet(ctx, hash[:])
if err != nil {
return nil, fmt.Errorf("failed to retrieve state MPT node: %w", err)
}
return node, nil
}
func (o *DebugClient) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
// First try retrieving with the new code prefix
code, err := o.dbGet(ctx, append(append(make([]byte, 0), rawdb.CodePrefix...), hash[:]...))
if err != nil {
// Fallback to the legacy un-prefixed version
code, err = o.dbGet(ctx, hash[:])
if err != nil {
return nil, fmt.Errorf("failed to retrieve contract code, using new and legacy keys, with codehash %s: %w", hash, err)
}
}
return code, nil
}
func (o *DebugClient) dbGet(ctx context.Context, key []byte) ([]byte, error) {
var node hexutil.Bytes
err := o.callContext(ctx, &node, "debug_dbGet", hexutil.Encode(key))
if err != nil {
return nil, fmt.Errorf("fetch error %x: %w", key, err)
}
return node, nil
}
...@@ -219,7 +219,7 @@ func (n numberID) CheckID(id eth.BlockID) error { ...@@ -219,7 +219,7 @@ func (n numberID) CheckID(id eth.BlockID) error {
return nil return nil
} }
func (s *EthClient) headerCall(ctx context.Context, method string, id rpcBlockID) (*HeaderInfo, error) { func (s *EthClient) headerCall(ctx context.Context, method string, id rpcBlockID) (eth.BlockInfo, error) {
var header *rpcHeader var header *rpcHeader
err := s.client.CallContext(ctx, &header, method, id.Arg(), false) // headers are just blocks without txs err := s.client.CallContext(ctx, &header, method, id.Arg(), false) // headers are just blocks without txs
if err != nil { if err != nil {
...@@ -239,7 +239,7 @@ func (s *EthClient) headerCall(ctx context.Context, method string, id rpcBlockID ...@@ -239,7 +239,7 @@ func (s *EthClient) headerCall(ctx context.Context, method string, id rpcBlockID
return info, nil return info, nil
} }
func (s *EthClient) blockCall(ctx context.Context, method string, id rpcBlockID) (*HeaderInfo, types.Transactions, error) { func (s *EthClient) blockCall(ctx context.Context, method string, id rpcBlockID) (eth.BlockInfo, types.Transactions, error) {
var block *rpcBlock var block *rpcBlock
err := s.client.CallContext(ctx, &block, method, id.Arg(), true) err := s.client.CallContext(ctx, &block, method, id.Arg(), true)
if err != nil { if err != nil {
...@@ -292,7 +292,7 @@ func (s *EthClient) ChainID(ctx context.Context) (*big.Int, error) { ...@@ -292,7 +292,7 @@ func (s *EthClient) ChainID(ctx context.Context) (*big.Int, error) {
func (s *EthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) { func (s *EthClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) {
if header, ok := s.headersCache.Get(hash); ok { if header, ok := s.headersCache.Get(hash); ok {
return header.(*HeaderInfo), nil return header.(eth.BlockInfo), nil
} }
return s.headerCall(ctx, "eth_getBlockByHash", hashID(hash)) return s.headerCall(ctx, "eth_getBlockByHash", hashID(hash))
} }
...@@ -310,7 +310,7 @@ func (s *EthClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth. ...@@ -310,7 +310,7 @@ func (s *EthClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.
func (s *EthClient) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, types.Transactions, error) { func (s *EthClient) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, types.Transactions, error) {
if header, ok := s.headersCache.Get(hash); ok { if header, ok := s.headersCache.Get(hash); ok {
if txs, ok := s.transactionsCache.Get(hash); ok { if txs, ok := s.transactionsCache.Get(hash); ok {
return header.(*HeaderInfo), txs.(types.Transactions), nil return header.(eth.BlockInfo), txs.(types.Transactions), nil
} }
} }
return s.blockCall(ctx, "eth_getBlockByHash", hashID(hash)) return s.blockCall(ctx, "eth_getBlockByHash", hashID(hash))
...@@ -356,10 +356,7 @@ func (s *EthClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (e ...@@ -356,10 +356,7 @@ func (s *EthClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (e
if v, ok := s.receiptsCache.Get(blockHash); ok { if v, ok := s.receiptsCache.Get(blockHash); ok {
job = v.(*receiptsFetchingJob) job = v.(*receiptsFetchingJob)
} else { } else {
txHashes := make([]common.Hash, len(txs)) txHashes := eth.TransactionsToHashes(txs)
for i := 0; i < len(txs); i++ {
txHashes[i] = txs[i].Hash()
}
job = NewReceiptsFetchingJob(s, s.client, s.maxBatchSize, eth.ToBlockID(info), info.ReceiptHash(), txHashes) job = NewReceiptsFetchingJob(s, s.client, s.maxBatchSize, eth.ToBlockID(info), info.ReceiptHash(), txHashes)
s.receiptsCache.Add(blockHash, job) s.receiptsCache.Add(blockHash, job)
} }
......
This diff is collapsed.
This diff is collapsed.
package testutils package testutils
import ( import (
"errors"
"math/big" "math/big"
"math/rand" "math/rand"
...@@ -22,6 +23,7 @@ type MockBlockInfo struct { ...@@ -22,6 +23,7 @@ type MockBlockInfo struct {
InfoBaseFee *big.Int InfoBaseFee *big.Int
InfoReceiptRoot common.Hash InfoReceiptRoot common.Hash
InfoGasUsed uint64 InfoGasUsed uint64
InfoHeaderRLP []byte
} }
func (l *MockBlockInfo) Hash() common.Hash { func (l *MockBlockInfo) Hash() common.Hash {
...@@ -68,6 +70,13 @@ func (l *MockBlockInfo) ID() eth.BlockID { ...@@ -68,6 +70,13 @@ func (l *MockBlockInfo) ID() eth.BlockID {
return eth.BlockID{Hash: l.InfoHash, Number: l.InfoNum} return eth.BlockID{Hash: l.InfoHash, Number: l.InfoNum}
} }
func (l *MockBlockInfo) HeaderRLP() ([]byte, error) {
if l.InfoHeaderRLP == nil {
return nil, errors.New("header rlp not available")
}
return l.InfoHeaderRLP, nil
}
func (l *MockBlockInfo) BlockRef() eth.L1BlockRef { func (l *MockBlockInfo) BlockRef() eth.L1BlockRef {
return eth.L1BlockRef{ return eth.L1BlockRef{
Hash: l.InfoHash, Hash: l.InfoHash,
......
This diff is collapsed.
This diff is collapsed.
...@@ -83,8 +83,8 @@ func (m *MockEthClient) PayloadByNumber(ctx context.Context, n uint64) (*eth.Exe ...@@ -83,8 +83,8 @@ func (m *MockEthClient) PayloadByNumber(ctx context.Context, n uint64) (*eth.Exe
return out[0].(*eth.ExecutionPayload), *out[1].(*error) return out[0].(*eth.ExecutionPayload), *out[1].(*error)
} }
func (m *MockEthClient) ExpectPayloadByNumber(hash common.Hash, payload *eth.ExecutionPayload, err error) { func (m *MockEthClient) ExpectPayloadByNumber(n uint64, payload *eth.ExecutionPayload, err error) {
m.Mock.On("PayloadByNumber", hash).Once().Return(payload, &err) m.Mock.On("PayloadByNumber", n).Once().Return(payload, &err)
} }
func (m *MockEthClient) PayloadByLabel(ctx context.Context, label eth.BlockLabel) (*eth.ExecutionPayload, error) { func (m *MockEthClient) PayloadByLabel(ctx context.Context, label eth.BlockLabel) (*eth.ExecutionPayload, error) {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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