From 32bd79ec9a7fe5ffe2be26d77811740e1d7bd0f5 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Mon, 13 Mar 2023 09:13:50 -0700 Subject: [PATCH 001/404] batch-submitter: allow deposit only batches Fixes a long standing bug that allows the batch submitter to send batches that only contain deposit transactions. Includes test coverage of the batch creation. The on chain code requires that there must be at least one context. A context contains information about the queue transactions that should be included. It makes no sense to error when there is a context but no sequencer txs because the context must be submitted to the chain. --- .changeset/fresh-pots-move.md | 5 ++ .../drivers/sequencer/batch_test.go | 74 +++++++++++++++++++ batch-submitter/drivers/sequencer/encoding.go | 8 -- .../valid_append_sequencer_batch_params.json | 4 +- 4 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 .changeset/fresh-pots-move.md diff --git a/.changeset/fresh-pots-move.md b/.changeset/fresh-pots-move.md new file mode 100644 index 000000000..774dd2690 --- /dev/null +++ b/.changeset/fresh-pots-move.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/batch-submitter-service': patch +--- + +Allow deposit only batches diff --git a/batch-submitter/drivers/sequencer/batch_test.go b/batch-submitter/drivers/sequencer/batch_test.go index f9fb93ec5..9a8e1f276 100644 --- a/batch-submitter/drivers/sequencer/batch_test.go +++ b/batch-submitter/drivers/sequencer/batch_test.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum-optimism/optimism/batch-submitter/drivers/sequencer" l2common "github.com/ethereum-optimism/optimism/l2geth/common" + "github.com/ethereum-optimism/optimism/l2geth/core/types" l2types "github.com/ethereum-optimism/optimism/l2geth/core/types" "github.com/stretchr/testify/require" ) @@ -47,3 +48,76 @@ func TestBatchElementFromBlock(t *testing.T) { require.False(t, element.IsSequencerTx()) require.Nil(t, element.Tx) } + +func TestGenSequencerParams(t *testing.T) { + tx := types.NewTransaction(0, l2common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{}) + + shouldStartAtElement := uint64(1) + blockOffset := uint64(1) + batches := []sequencer.BatchElement{ + {Timestamp: 1, BlockNumber: 1}, + {Timestamp: 1, BlockNumber: 1, Tx: sequencer.NewCachedTx(tx)}, + } + + params, err := sequencer.GenSequencerBatchParams(shouldStartAtElement, blockOffset, batches) + require.NoError(t, err) + + require.Equal(t, uint64(0), params.ShouldStartAtElement) + require.Equal(t, uint64(len(batches)), params.TotalElementsToAppend) + require.Equal(t, len(batches), len(params.Contexts)) + // There is only 1 sequencer tx + require.Equal(t, 1, len(params.Txs)) + + // There are 2 contexts + // The first context contains the deposit + context1 := params.Contexts[0] + require.Equal(t, uint64(0), context1.NumSequencedTxs) + require.Equal(t, uint64(1), context1.NumSubsequentQueueTxs) + require.Equal(t, uint64(1), context1.Timestamp) + require.Equal(t, uint64(1), context1.BlockNumber) + + // The second context contains the sequencer tx + context2 := params.Contexts[1] + require.Equal(t, uint64(1), context2.NumSequencedTxs) + require.Equal(t, uint64(0), context2.NumSubsequentQueueTxs) + require.Equal(t, uint64(1), context2.Timestamp) + require.Equal(t, uint64(1), context2.BlockNumber) +} + +func TestGenSequencerParamsOnlyDeposits(t *testing.T) { + shouldStartAtElement := uint64(1) + blockOffset := uint64(1) + batches := []sequencer.BatchElement{ + {Timestamp: 1, BlockNumber: 1}, + {Timestamp: 1, BlockNumber: 1}, + {Timestamp: 2, BlockNumber: 2}, + } + + params, err := sequencer.GenSequencerBatchParams(shouldStartAtElement, blockOffset, batches) + require.NoError(t, err) + + // The batches will pack deposits into the same context when their + // timestamps and blocknumbers are the same + require.Equal(t, uint64(0), params.ShouldStartAtElement) + require.Equal(t, uint64(len(batches)), params.TotalElementsToAppend) + // 2 deposits have the same timestamp + blocknumber, they go in the + // same context. 1 deposit has a different timestamp + blocknumber, + // it goes into a different context. Therefore there are 2 contexts + require.Equal(t, 2, len(params.Contexts)) + // No sequencer txs + require.Equal(t, 0, len(params.Txs)) + + // There are 2 contexts + // The first context contains the deposit + context1 := params.Contexts[0] + require.Equal(t, uint64(0), context1.NumSequencedTxs) + require.Equal(t, uint64(2), context1.NumSubsequentQueueTxs) + require.Equal(t, uint64(1), context1.Timestamp) + require.Equal(t, uint64(1), context1.BlockNumber) + + context2 := params.Contexts[1] + require.Equal(t, uint64(0), context2.NumSequencedTxs) + require.Equal(t, uint64(1), context2.NumSubsequentQueueTxs) + require.Equal(t, uint64(2), context2.Timestamp) + require.Equal(t, uint64(2), context2.BlockNumber) +} diff --git a/batch-submitter/drivers/sequencer/encoding.go b/batch-submitter/drivers/sequencer/encoding.go index 54c2066f7..e38b70bc1 100644 --- a/batch-submitter/drivers/sequencer/encoding.go +++ b/batch-submitter/drivers/sequencer/encoding.go @@ -222,11 +222,6 @@ func (p *AppendSequencerBatchParams) Write( return ErrMalformedBatch } - // There must be transactions if there are contexts - if len(p.Txs) == 0 && len(p.Contexts) != 0 { - return ErrMalformedBatch - } - // copy the contexts as to not malleate the struct // when it is a typed batch contexts := make([]BatchContext, 0, len(p.Contexts)+1) @@ -361,9 +356,6 @@ func (p *AppendSequencerBatchParams) Read(r io.Reader) error { if len(p.Contexts) == 0 && len(p.Txs) != 0 { return ErrMalformedBatch } - if len(p.Txs) == 0 && len(p.Contexts) != 0 { - return ErrMalformedBatch - } return closeReader() } else if err != nil { return err diff --git a/batch-submitter/drivers/sequencer/testdata/valid_append_sequencer_batch_params.json b/batch-submitter/drivers/sequencer/testdata/valid_append_sequencer_batch_params.json index 299f72a40..9152569fc 100644 --- a/batch-submitter/drivers/sequencer/testdata/valid_append_sequencer_batch_params.json +++ b/batch-submitter/drivers/sequencer/testdata/valid_append_sequencer_batch_params.json @@ -46,7 +46,7 @@ } ], "txs": [], - "error": true + "error": false }, { "name": "multiple-contexts-no-txs", @@ -80,7 +80,7 @@ } ], "txs": [], - "error": true + "error": false }, { "name": "complex", From e80cfdb2de7d1aa1948c0b5837e90a98c2e516ce Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 13 Mar 2023 13:15:55 -0400 Subject: [PATCH 002/404] start channel building tests --- op-batcher/batcher/channel_builder.go | 4 +- op-batcher/batcher/channel_builder_test.go | 79 ++++++++++++++++++++++ op-batcher/batcher/channel_manager.go | 2 +- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 op-batcher/batcher/channel_builder_test.go diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 2e79ff11f..e0591798d 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -79,7 +79,9 @@ type channelBuilder struct { frames []taggedData } -func newChannelBuilder(cfg ChannelConfig) (*channelBuilder, error) { +// NewChannelBuilder creates a new channel builder or returns an error if the +// channel out could not be created. +func NewChannelBuilder(cfg ChannelConfig) (*channelBuilder, error) { co, err := derive.NewChannelOut() if err != nil { return nil, err diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go new file mode 100644 index 000000000..d3ff7770b --- /dev/null +++ b/op-batcher/batcher/channel_builder_test.go @@ -0,0 +1,79 @@ +package batcher + +import ( + "bytes" + "testing" + + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/stretchr/testify/require" +) + +var defaultTestConfig = ChannelConfig{ + SeqWindowSize: 15, + ChannelTimeout: 40, + MaxChannelDuration: 1, + SubSafetyMargin: 4, + MaxFrameSize: 120000, + TargetFrameSize: 100000, + TargetNumFrames: 1, + ApproxComprRatio: 0.4, +} + +// TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame +func TestBuilderNextFrame(t *testing.T) { + cb, err := NewChannelBuilder(defaultTestConfig) + require.NoError(t, err) + + // Mock the internals of `channelBuilder.outputFrame` + // to construct a single frame + co := cb.co + var buf bytes.Buffer + fn, err := co.OutputFrame(&buf, defaultTestConfig.MaxFrameSize) + require.NoError(t, err) + + // Push one frame into to the channel builder + expectedTx := txID{chID: co.ID(), frameNumber: fn} + expectedBytes := buf.Bytes() + cb.PushFrame(expectedTx, expectedBytes) + + // There should only be 1 frame in the channel builder + require.Equal(t, 1, cb.NumFrames()) + + // We should be able to increment to the next frame + constructedTx, constructedBytes := cb.NextFrame() + require.Equal(t, expectedTx, constructedTx) + require.Equal(t, expectedBytes, constructedBytes) + require.Equal(t, 0, cb.NumFrames()) + + // The next call should panic since the length of frames is 0 + defer func() { _ = recover() }() + cb.NextFrame() + + // If we get here, `NextFrame` did not panic as expected + t.Errorf("did not panic") +} + +// TestBuilderInvalidFrameId tests that a panic is thrown when a frame is pushed with an invalid frame id +func TestBuilderWrongFramePanic(t *testing.T) { + cb, err := NewChannelBuilder(defaultTestConfig) + require.NoError(t, err) + + // Mock the internals of `channelBuilder.outputFrame` + // to construct a single frame + co, err := derive.NewChannelOut() + require.NoError(t, err) + var buf bytes.Buffer + fn, err := co.OutputFrame(&buf, defaultTestConfig.MaxFrameSize) + require.NoError(t, err) + + // The frame push should panic since we constructed a new channel out + // so the channel out id won't match + defer func() { _ = recover() }() + + // Push one frame into to the channel builder + tx := txID{chID: co.ID(), frameNumber: fn} + cb.PushFrame(tx, buf.Bytes()) + + // If we get here, `PushFrame` did not panic as expected + t.Errorf("did not panic") +} diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 0e4ae8c94..b94786772 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -229,7 +229,7 @@ func (s *channelManager) ensurePendingChannel(l1Head eth.BlockID) error { return nil } - cb, err := newChannelBuilder(s.cfg) + cb, err := NewChannelBuilder(s.cfg) if err != nil { return fmt.Errorf("creating new channel: %w", err) } From 98a800f859519b7624a13ee439d24ceec805d3d4 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 13 Mar 2023 14:59:30 -0400 Subject: [PATCH 003/404] channel builder test suite --- op-batcher/batcher/channel_builder.go | 4 +- op-batcher/batcher/channel_builder_test.go | 68 +++++++++++++--------- op-batcher/batcher/channel_config_test.go | 40 +++++++++++++ op-batcher/batcher/channel_manager.go | 2 +- 4 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 op-batcher/batcher/channel_config_test.go diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index e0591798d..965e5a9f0 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -79,9 +79,9 @@ type channelBuilder struct { frames []taggedData } -// NewChannelBuilder creates a new channel builder or returns an error if the +// newChannelBuilder creates a new channel builder or returns an error if the // channel out could not be created. -func NewChannelBuilder(cfg ChannelConfig) (*channelBuilder, error) { +func newChannelBuilder(cfg ChannelConfig) (*channelBuilder, error) { co, err := derive.NewChannelOut() if err != nil { return nil, err diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index d3ff7770b..cc3892c8a 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -5,31 +5,45 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) -var defaultTestConfig = ChannelConfig{ - SeqWindowSize: 15, - ChannelTimeout: 40, - MaxChannelDuration: 1, - SubSafetyMargin: 4, - MaxFrameSize: 120000, - TargetFrameSize: 100000, - TargetNumFrames: 1, - ApproxComprRatio: 0.4, +// ChannelBuilderTestSuite encapsulates testing on the ChannelBuilder. +type ChannelBuilderTestSuite struct { + suite.Suite + channelConfig ChannelConfig +} + +// SetupTest sets up the test suite. +func (testSuite *ChannelBuilderTestSuite) SetupTest() { + testSuite.channelConfig = ChannelConfig{ + SeqWindowSize: 15, + ChannelTimeout: 40, + MaxChannelDuration: 1, + SubSafetyMargin: 4, + MaxFrameSize: 120000, + TargetFrameSize: 100000, + TargetNumFrames: 1, + ApproxComprRatio: 0.4, + } +} + +// TestChannelBuilder runs the ChannelBuilderTestSuite. +func TestChannelBuilder(t *testing.T) { + suite.Run(t, new(ChannelBuilderTestSuite)) } // TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame -func TestBuilderNextFrame(t *testing.T) { - cb, err := NewChannelBuilder(defaultTestConfig) - require.NoError(t, err) +func (testSuite *ChannelBuilderTestSuite) TestBuilderNextFrame() { + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) // Mock the internals of `channelBuilder.outputFrame` // to construct a single frame co := cb.co var buf bytes.Buffer - fn, err := co.OutputFrame(&buf, defaultTestConfig.MaxFrameSize) - require.NoError(t, err) + fn, err := co.OutputFrame(&buf, testSuite.channelConfig.MaxFrameSize) + testSuite.NoError(err) // Push one frame into to the channel builder expectedTx := txID{chID: co.ID(), frameNumber: fn} @@ -37,34 +51,34 @@ func TestBuilderNextFrame(t *testing.T) { cb.PushFrame(expectedTx, expectedBytes) // There should only be 1 frame in the channel builder - require.Equal(t, 1, cb.NumFrames()) + testSuite.Equal(1, cb.NumFrames()) // We should be able to increment to the next frame constructedTx, constructedBytes := cb.NextFrame() - require.Equal(t, expectedTx, constructedTx) - require.Equal(t, expectedBytes, constructedBytes) - require.Equal(t, 0, cb.NumFrames()) + testSuite.Equal(expectedTx, constructedTx) + testSuite.Equal(expectedBytes, constructedBytes) + testSuite.Equal(0, cb.NumFrames()) // The next call should panic since the length of frames is 0 defer func() { _ = recover() }() cb.NextFrame() // If we get here, `NextFrame` did not panic as expected - t.Errorf("did not panic") + testSuite.T().Errorf("did not panic") } // TestBuilderInvalidFrameId tests that a panic is thrown when a frame is pushed with an invalid frame id -func TestBuilderWrongFramePanic(t *testing.T) { - cb, err := NewChannelBuilder(defaultTestConfig) - require.NoError(t, err) +func (testSuite *ChannelBuilderTestSuite) TestBuilderWrongFramePanic() { + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) // Mock the internals of `channelBuilder.outputFrame` // to construct a single frame co, err := derive.NewChannelOut() - require.NoError(t, err) + testSuite.NoError(err) var buf bytes.Buffer - fn, err := co.OutputFrame(&buf, defaultTestConfig.MaxFrameSize) - require.NoError(t, err) + fn, err := co.OutputFrame(&buf, testSuite.channelConfig.MaxFrameSize) + testSuite.NoError(err) // The frame push should panic since we constructed a new channel out // so the channel out id won't match @@ -75,5 +89,5 @@ func TestBuilderWrongFramePanic(t *testing.T) { cb.PushFrame(tx, buf.Bytes()) // If we get here, `PushFrame` did not panic as expected - t.Errorf("did not panic") + testSuite.T().Errorf("did not panic") } diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go new file mode 100644 index 000000000..03abe1ad8 --- /dev/null +++ b/op-batcher/batcher/channel_config_test.go @@ -0,0 +1,40 @@ +package batcher_test + +import ( + "math" + "testing" + + "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/stretchr/testify/require" +) + +// TestInputThreshold tests the [ChannelConfig.InputThreshold] function. +func TestInputThreshold(t *testing.T) { + // Construct an empty channel config + config := batcher.ChannelConfig{ + SeqWindowSize: 15, + ChannelTimeout: 40, + MaxChannelDuration: 1, + SubSafetyMargin: 4, + MaxFrameSize: 120000, + TargetFrameSize: 100000, + TargetNumFrames: 1, + ApproxComprRatio: 0.4, + } + + // The input threshold is calculated as: (targetNumFrames * targetFrameSize) / approxComprRatio + // Here we see that 100,000 / 0.4 = 100,000 * 2.5 = 250,000 + inputThreshold := config.InputThreshold() + require.Equal(t, uint64(250_000), inputThreshold) + + // Set the approximate compression ratio to 0 + // Logically, this represents infinite compression, + // so there is no threshold on the size of the input. + // In practice, this should never be set to 0. + config.ApproxComprRatio = 0 + + // The input threshold will overflow to the max uint64 value + receivedThreshold := config.InputThreshold() + var maxUint64 uint64 = math.MaxUint64 + require.Equal(t, maxUint64, receivedThreshold) +} diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index b94786772..0e4ae8c94 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -229,7 +229,7 @@ func (s *channelManager) ensurePendingChannel(l1Head eth.BlockID) error { return nil } - cb, err := NewChannelBuilder(s.cfg) + cb, err := newChannelBuilder(s.cfg) if err != nil { return fmt.Errorf("creating new channel: %w", err) } From 53d9465e79f6b5b804253069c8cb6019b820eedf Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 13 Mar 2023 17:10:02 -0400 Subject: [PATCH 004/404] more channel builder tests :test_tube: --- op-batcher/batcher/channel_builder.go | 2 +- op-batcher/batcher/channel_builder_test.go | 151 +++++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 965e5a9f0..a4e5b5394 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -207,7 +207,7 @@ func (c *channelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) { } // checkTimeout checks if the channel is timed out at the given block number and -// in this case marks the channel as full, if it wasn't full alredy. +// in this case marks the channel as full, if it wasn't full already. func (c *channelBuilder) checkTimeout(blockNum uint64) { if !c.IsFull() && c.TimedOut(blockNum) { c.setFullErr(c.timeoutReason) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index cc3892c8a..fbc6102ac 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -2,9 +2,15 @@ package batcher import ( "bytes" + "math/big" "testing" + "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/suite" ) @@ -91,3 +97,148 @@ func (testSuite *ChannelBuilderTestSuite) TestBuilderWrongFramePanic() { // If we get here, `PushFrame` did not panic as expected testSuite.T().Errorf("did not panic") } + +func addNonsenseBlock(cb *channelBuilder) error { + lBlock := types.NewBlock(&types.Header{ + BaseFee: big.NewInt(10), + Difficulty: common.Big0, + Number: big.NewInt(100), + }, nil, nil, nil, trie.NewStackTrie(nil)) + l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) + if err != nil { + return err + } + txs := []*types.Transaction{types.NewTx(l1InfoTx)} + a := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, txs, nil, nil, trie.NewStackTrie(nil)) + err = cb.AddBlock(a) + return err +} + +// TestOutputFrames tests the OutputFrames function +func (testSuite *ChannelBuilderTestSuite) TestOutputFrames() { + // Lower the max frame size so that we can test + testSuite.channelConfig.MaxFrameSize = 2 + + // Construct the channel builder + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + testSuite.False(cb.IsFull()) + testSuite.Equal(0, cb.NumFrames()) + + // Calling OutputFrames without having called [AddBlock] + // should return `nil`. + testSuite.Nil(cb.OutputFrames()) + + // There should be no ready bytes yet + readyBytes := cb.co.ReadyBytes() + testSuite.Equal(0, readyBytes) + + // Let's add a block + err = addNonsenseBlock(cb) + testSuite.NoError(err) + + // Check how many ready bytes + readyBytes = cb.co.ReadyBytes() + testSuite.Equal(2, readyBytes) + + testSuite.Equal(0, cb.NumFrames()) + + // The channel should not be full + // but we want to output the frames for testing anyways + isFull := cb.IsFull() + testSuite.False(isFull) + + // Since we manually set the max frame size to 2, + // we should be able to compress the two frames now + err = cb.OutputFrames() + testSuite.NoError(err) + + // There should be one frame in the channel builder now + testSuite.Equal(1, cb.NumFrames()) + + // There should no longer be any ready bytes + readyBytes = cb.co.ReadyBytes() + testSuite.Equal(0, readyBytes) +} + +// TestBuilderAddBlock tests the AddBlock function +func (testSuite *ChannelBuilderTestSuite) TestBuilderAddBlock() { + // Lower the max frame size so that we can batch + testSuite.channelConfig.MaxFrameSize = 2 + + // Configure the Input Threshold params so we observe a full channel + // In reality, we only need the input bytes (74) below to be greater than + // or equal to the input threshold (3 * 2) / 1 = 6 + testSuite.channelConfig.TargetFrameSize = 3 + testSuite.channelConfig.TargetNumFrames = 2 + testSuite.channelConfig.ApproxComprRatio = 1 + + // Construct the channel builder + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + // Add a nonsense block to the channel builder + err = addNonsenseBlock(cb) + testSuite.NoError(err) + + // Check the fields reset in the AddBlock function + testSuite.Equal(74, cb.co.InputBytes()) + testSuite.Equal(1, len(cb.blocks)) + testSuite.Equal(0, len(cb.frames)) + testSuite.True(cb.IsFull()) + + // Since the channel output is full, the next call to AddBlock + // should return the channel out full error + err = addNonsenseBlock(cb) + testSuite.ErrorIs(err, ErrInputTargetReached) +} + +// TestBuilderReset tests the Reset function +func (testSuite *ChannelBuilderTestSuite) TestBuilderReset() { + // Lower the max frame size so that we can batch + testSuite.channelConfig.MaxFrameSize = 2 + + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + // Add a nonsense block to the channel builder + err = addNonsenseBlock(cb) + testSuite.NoError(err) + + // Check the fields reset in the Reset function + testSuite.Equal(1, len(cb.blocks)) + testSuite.Equal(0, len(cb.frames)) + // Timeout should be updated in the AddBlock internal call to `updateSwTimeout` + timeout := uint64(100) + cb.cfg.SeqWindowSize - cb.cfg.SubSafetyMargin + testSuite.Equal(timeout, cb.timeout) + testSuite.Nil(cb.fullErr) + + // Output frames so we can set the channel builder frames + err = cb.OutputFrames() + testSuite.NoError(err) + + // Add another block to increment the block count + err = addNonsenseBlock(cb) + testSuite.NoError(err) + + // Check the fields reset in the Reset function + testSuite.Equal(2, len(cb.blocks)) + testSuite.Equal(1, len(cb.frames)) + testSuite.Equal(timeout, cb.timeout) + testSuite.Nil(cb.fullErr) + + // Reset the channel builder + err = cb.Reset() + testSuite.NoError(err) + + // Check the fields reset in the Reset function + testSuite.Equal(0, len(cb.blocks)) + testSuite.Equal(0, len(cb.frames)) + testSuite.Equal(uint64(0), cb.timeout) + testSuite.Nil(cb.fullErr) + testSuite.Equal(0, cb.co.InputBytes()) + testSuite.Equal(0, cb.co.ReadyBytes()) +} From e79b17c603f72d9d92c137a325e3c9ab3069adcb Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 13 Mar 2023 17:45:14 -0400 Subject: [PATCH 005/404] frame publishing tests --- op-batcher/batcher/channel_builder_test.go | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index fbc6102ac..abcf2d68c 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -242,3 +242,85 @@ func (testSuite *ChannelBuilderTestSuite) TestBuilderReset() { testSuite.Equal(0, cb.co.InputBytes()) testSuite.Equal(0, cb.co.ReadyBytes()) } + +// TestBuilderRegisterL1Block tests the RegisterL1Block function +func (testSuite *ChannelBuilderTestSuite) TestBuilderRegisterL1Block() { + // Construct the channel builder + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + // Assert params modified in RegisterL1Block + testSuite.Equal(uint64(1), testSuite.channelConfig.MaxChannelDuration) + testSuite.Equal(uint64(0), cb.timeout) + + // Register a new L1 block + cb.RegisterL1Block(uint64(100)) + + // Assert params modified in RegisterL1Block + testSuite.Equal(uint64(1), testSuite.channelConfig.MaxChannelDuration) + testSuite.Equal(uint64(101), cb.timeout) +} + +// TestBuilderRegisterL1BlockZeroMaxChannelDuration tests the RegisterL1Block function +func (testSuite *ChannelBuilderTestSuite) TestBuilderRegisterL1BlockZeroMaxChannelDuration() { + // Set the max channel duration to 0 + testSuite.channelConfig.MaxChannelDuration = 0 + + // Construct the channel builder + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + // Assert params modified in RegisterL1Block + testSuite.Equal(uint64(0), testSuite.channelConfig.MaxChannelDuration) + testSuite.Equal(uint64(0), cb.timeout) + + // Register a new L1 block + cb.RegisterL1Block(uint64(100)) + + // Since the max channel duration is set to 0, + // the L1 block register should not update the timeout + testSuite.Equal(uint64(0), testSuite.channelConfig.MaxChannelDuration) + testSuite.Equal(uint64(0), cb.timeout) +} + +// TestFramePublished tests the FramePublished function +func (testSuite *ChannelBuilderTestSuite) TestFramePublished() { + // Construct the channel builder + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + // Let's say the block number is fed in as 100 + // and the channel timeout is 1000 + l1BlockNum := uint64(100) + cb.cfg.ChannelTimeout = uint64(1000) + cb.cfg.SubSafetyMargin = 100 + + // Then the frame published will update the timeout + cb.FramePublished(l1BlockNum) + + // Now the timeout will be 1000 + testSuite.Equal(uint64(1000), cb.timeout) +} + +// TestFramePublishedUnderflows tests the FramePublished function +// +// Realistically, this will not happen because the [ChannelTimeout] is _known_ +// to be greater than the [SubSafetyMargin] when the ChannelConfig is configured +// via cli flags / environment variables. +func (testSuite *ChannelBuilderTestSuite) TestFramePublishedUnderflows() { + // Construct the channel builder + cb, err := newChannelBuilder(testSuite.channelConfig) + testSuite.NoError(err) + + // Let's say the block number is fed in as 0 + // and the channel timeout is < the sub safety margin + l1BlockNum := uint64(0) + cb.cfg.ChannelTimeout = uint64(1) + cb.cfg.SubSafetyMargin = uint64(2) + + // Then the frame published will underflow the timeout + cb.FramePublished(l1BlockNum) + + // Now the timeout will be the max uint64 + testSuite.Equal(uint64(0xffffffffffffffff), cb.timeout) +} From d1718ac27cb3cc411244a7c70ba83263ca897ae8 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 13 Mar 2023 17:49:08 -0400 Subject: [PATCH 006/404] wut --- op-batcher/batcher/channel_config_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index 03abe1ad8..0eff558a2 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -1,7 +1,6 @@ package batcher_test import ( - "math" "testing" "github.com/ethereum-optimism/optimism/op-batcher/batcher" @@ -35,6 +34,5 @@ func TestInputThreshold(t *testing.T) { // The input threshold will overflow to the max uint64 value receivedThreshold := config.InputThreshold() - var maxUint64 uint64 = math.MaxUint64 - require.Equal(t, maxUint64, receivedThreshold) + require.Equal(t, uint64(0xffffffffffffffff), receivedThreshold) } From 80377f723e0fa12e780a74686bf61d1683ee4ff7 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 13 Mar 2023 17:53:07 -0400 Subject: [PATCH 007/404] exceeds max ? --- op-batcher/batcher/channel_config_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index 0eff558a2..1682a8561 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -34,5 +34,6 @@ func TestInputThreshold(t *testing.T) { // The input threshold will overflow to the max uint64 value receivedThreshold := config.InputThreshold() - require.Equal(t, uint64(0xffffffffffffffff), receivedThreshold) + max := config.TargetNumFrames * int(config.TargetFrameSize) + require.True(t, receivedThreshold > uint64(max)) } From 404cdd79b7a59fefbdf6ac373de831214622666d Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 10:28:07 -0400 Subject: [PATCH 008/404] revert channel builder test suite back to bare metal testing --- op-batcher/batcher/channel_builder_test.go | 296 ++++++++++----------- 1 file changed, 136 insertions(+), 160 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index abcf2d68c..e19239935 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -7,49 +7,59 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" - "github.com/stretchr/testify/suite" + "github.com/stretchr/testify/require" ) -// ChannelBuilderTestSuite encapsulates testing on the ChannelBuilder. -type ChannelBuilderTestSuite struct { - suite.Suite - channelConfig ChannelConfig +var defaultTestChannelConfig = ChannelConfig{ + SeqWindowSize: 15, + ChannelTimeout: 40, + MaxChannelDuration: 1, + SubSafetyMargin: 4, + MaxFrameSize: 120000, + TargetFrameSize: 100000, + TargetNumFrames: 1, + ApproxComprRatio: 0.4, } -// SetupTest sets up the test suite. -func (testSuite *ChannelBuilderTestSuite) SetupTest() { - testSuite.channelConfig = ChannelConfig{ - SeqWindowSize: 15, - ChannelTimeout: 40, - MaxChannelDuration: 1, - SubSafetyMargin: 4, - MaxFrameSize: 120000, - TargetFrameSize: 100000, - TargetNumFrames: 1, - ApproxComprRatio: 0.4, +// addNonsenseBlock is a helper function that adds a nonsense block +// to the channel builder using the [channelBuilder.AddBlock] method. +func addNonsenseBlock(cb *channelBuilder) error { + lBlock := types.NewBlock(&types.Header{ + BaseFee: big.NewInt(10), + Difficulty: common.Big0, + Number: big.NewInt(100), + }, nil, nil, nil, trie.NewStackTrie(nil)) + l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) + if err != nil { + return err } -} - -// TestChannelBuilder runs the ChannelBuilderTestSuite. -func TestChannelBuilder(t *testing.T) { - suite.Run(t, new(ChannelBuilderTestSuite)) + txs := []*types.Transaction{types.NewTx(l1InfoTx)} + a := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, txs, nil, nil, trie.NewStackTrie(nil)) + err = cb.AddBlock(a) + return err } // TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame -func (testSuite *ChannelBuilderTestSuite) TestBuilderNextFrame() { - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) +func TestBuilderNextFrame(t *testing.T) { + channelConfig := defaultTestChannelConfig + + // Create a new channel builder + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Mock the internals of `channelBuilder.outputFrame` // to construct a single frame co := cb.co var buf bytes.Buffer - fn, err := co.OutputFrame(&buf, testSuite.channelConfig.MaxFrameSize) - testSuite.NoError(err) + fn, err := co.OutputFrame(&buf, channelConfig.MaxFrameSize) + require.NoError(t, err) // Push one frame into to the channel builder expectedTx := txID{chID: co.ID(), frameNumber: fn} @@ -57,237 +67,226 @@ func (testSuite *ChannelBuilderTestSuite) TestBuilderNextFrame() { cb.PushFrame(expectedTx, expectedBytes) // There should only be 1 frame in the channel builder - testSuite.Equal(1, cb.NumFrames()) + require.Equal(t, 1, cb.NumFrames()) // We should be able to increment to the next frame constructedTx, constructedBytes := cb.NextFrame() - testSuite.Equal(expectedTx, constructedTx) - testSuite.Equal(expectedBytes, constructedBytes) - testSuite.Equal(0, cb.NumFrames()) + require.Equal(t, expectedTx, constructedTx) + require.Equal(t, expectedBytes, constructedBytes) + require.Equal(t, 0, cb.NumFrames()) // The next call should panic since the length of frames is 0 - defer func() { _ = recover() }() - cb.NextFrame() - - // If we get here, `NextFrame` did not panic as expected - testSuite.T().Errorf("did not panic") + require.PanicsWithValue(t, "no next frame", func() { cb.NextFrame() }) } // TestBuilderInvalidFrameId tests that a panic is thrown when a frame is pushed with an invalid frame id -func (testSuite *ChannelBuilderTestSuite) TestBuilderWrongFramePanic() { - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) +func TestBuilderWrongFramePanic(t *testing.T) { + channelConfig := defaultTestChannelConfig + + // Construct a channel builder + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Mock the internals of `channelBuilder.outputFrame` // to construct a single frame co, err := derive.NewChannelOut() - testSuite.NoError(err) + require.NoError(t, err) var buf bytes.Buffer - fn, err := co.OutputFrame(&buf, testSuite.channelConfig.MaxFrameSize) - testSuite.NoError(err) + fn, err := co.OutputFrame(&buf, channelConfig.MaxFrameSize) + require.NoError(t, err) // The frame push should panic since we constructed a new channel out // so the channel out id won't match - defer func() { _ = recover() }() - - // Push one frame into to the channel builder - tx := txID{chID: co.ID(), frameNumber: fn} - cb.PushFrame(tx, buf.Bytes()) - - // If we get here, `PushFrame` did not panic as expected - testSuite.T().Errorf("did not panic") -} - -func addNonsenseBlock(cb *channelBuilder) error { - lBlock := types.NewBlock(&types.Header{ - BaseFee: big.NewInt(10), - Difficulty: common.Big0, - Number: big.NewInt(100), - }, nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) - if err != nil { - return err - } - txs := []*types.Transaction{types.NewTx(l1InfoTx)} - a := types.NewBlock(&types.Header{ - Number: big.NewInt(0), - }, txs, nil, nil, trie.NewStackTrie(nil)) - err = cb.AddBlock(a) - return err + require.PanicsWithValue(t, "wrong channel", func() { + tx := txID{chID: co.ID(), frameNumber: fn} + cb.PushFrame(tx, buf.Bytes()) + }) } // TestOutputFrames tests the OutputFrames function -func (testSuite *ChannelBuilderTestSuite) TestOutputFrames() { +func TestOutputFrames(t *testing.T) { + channelConfig := defaultTestChannelConfig + // Lower the max frame size so that we can test - testSuite.channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = 2 // Construct the channel builder - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) - testSuite.False(cb.IsFull()) - testSuite.Equal(0, cb.NumFrames()) + require.False(t, cb.IsFull()) + require.Equal(t, 0, cb.NumFrames()) // Calling OutputFrames without having called [AddBlock] - // should return `nil`. - testSuite.Nil(cb.OutputFrames()) + // should return no error + require.NoError(t, cb.OutputFrames()) // There should be no ready bytes yet readyBytes := cb.co.ReadyBytes() - testSuite.Equal(0, readyBytes) + require.Equal(t, 0, readyBytes) // Let's add a block err = addNonsenseBlock(cb) - testSuite.NoError(err) + require.NoError(t, err) // Check how many ready bytes readyBytes = cb.co.ReadyBytes() - testSuite.Equal(2, readyBytes) + require.Equal(t, 2, readyBytes) - testSuite.Equal(0, cb.NumFrames()) + require.Equal(t, 0, cb.NumFrames()) // The channel should not be full // but we want to output the frames for testing anyways isFull := cb.IsFull() - testSuite.False(isFull) + require.False(t, isFull) // Since we manually set the max frame size to 2, // we should be able to compress the two frames now err = cb.OutputFrames() - testSuite.NoError(err) + require.NoError(t, err) // There should be one frame in the channel builder now - testSuite.Equal(1, cb.NumFrames()) + require.Equal(t, 1, cb.NumFrames()) // There should no longer be any ready bytes readyBytes = cb.co.ReadyBytes() - testSuite.Equal(0, readyBytes) + require.Equal(t, 0, readyBytes) } // TestBuilderAddBlock tests the AddBlock function -func (testSuite *ChannelBuilderTestSuite) TestBuilderAddBlock() { +func TestBuilderAddBlock(t *testing.T) { + channelConfig := defaultTestChannelConfig + // Lower the max frame size so that we can batch - testSuite.channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = 2 // Configure the Input Threshold params so we observe a full channel // In reality, we only need the input bytes (74) below to be greater than // or equal to the input threshold (3 * 2) / 1 = 6 - testSuite.channelConfig.TargetFrameSize = 3 - testSuite.channelConfig.TargetNumFrames = 2 - testSuite.channelConfig.ApproxComprRatio = 1 + channelConfig.TargetFrameSize = 3 + channelConfig.TargetNumFrames = 2 + channelConfig.ApproxComprRatio = 1 // Construct the channel builder - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Add a nonsense block to the channel builder err = addNonsenseBlock(cb) - testSuite.NoError(err) + require.NoError(t, err) // Check the fields reset in the AddBlock function - testSuite.Equal(74, cb.co.InputBytes()) - testSuite.Equal(1, len(cb.blocks)) - testSuite.Equal(0, len(cb.frames)) - testSuite.True(cb.IsFull()) + require.Equal(t, 74, cb.co.InputBytes()) + require.Equal(t, 1, len(cb.blocks)) + require.Equal(t, 0, len(cb.frames)) + require.True(t, cb.IsFull()) // Since the channel output is full, the next call to AddBlock // should return the channel out full error err = addNonsenseBlock(cb) - testSuite.ErrorIs(err, ErrInputTargetReached) + require.ErrorIs(t, err, ErrInputTargetReached) } // TestBuilderReset tests the Reset function -func (testSuite *ChannelBuilderTestSuite) TestBuilderReset() { +func TestBuilderReset(t *testing.T) { + channelConfig := defaultTestChannelConfig + // Lower the max frame size so that we can batch - testSuite.channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = 2 - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Add a nonsense block to the channel builder err = addNonsenseBlock(cb) - testSuite.NoError(err) + require.NoError(t, err) // Check the fields reset in the Reset function - testSuite.Equal(1, len(cb.blocks)) - testSuite.Equal(0, len(cb.frames)) + require.Equal(t, 1, len(cb.blocks)) + require.Equal(t, 0, len(cb.frames)) // Timeout should be updated in the AddBlock internal call to `updateSwTimeout` timeout := uint64(100) + cb.cfg.SeqWindowSize - cb.cfg.SubSafetyMargin - testSuite.Equal(timeout, cb.timeout) - testSuite.Nil(cb.fullErr) + require.Equal(t, timeout, cb.timeout) + require.NoError(t, cb.fullErr) // Output frames so we can set the channel builder frames err = cb.OutputFrames() - testSuite.NoError(err) + require.NoError(t, err) // Add another block to increment the block count err = addNonsenseBlock(cb) - testSuite.NoError(err) + require.NoError(t, err) // Check the fields reset in the Reset function - testSuite.Equal(2, len(cb.blocks)) - testSuite.Equal(1, len(cb.frames)) - testSuite.Equal(timeout, cb.timeout) - testSuite.Nil(cb.fullErr) + require.Equal(t, 2, len(cb.blocks)) + require.Equal(t, 1, len(cb.frames)) + require.Equal(t, timeout, cb.timeout) + require.NoError(t, cb.fullErr) // Reset the channel builder err = cb.Reset() - testSuite.NoError(err) + require.NoError(t, err) // Check the fields reset in the Reset function - testSuite.Equal(0, len(cb.blocks)) - testSuite.Equal(0, len(cb.frames)) - testSuite.Equal(uint64(0), cb.timeout) - testSuite.Nil(cb.fullErr) - testSuite.Equal(0, cb.co.InputBytes()) - testSuite.Equal(0, cb.co.ReadyBytes()) + require.Equal(t, 0, len(cb.blocks)) + require.Equal(t, 0, len(cb.frames)) + require.Equal(t, uint64(0), cb.timeout) + require.NoError(t, cb.fullErr) + require.Equal(t, 0, cb.co.InputBytes()) + require.Equal(t, 0, cb.co.ReadyBytes()) } // TestBuilderRegisterL1Block tests the RegisterL1Block function -func (testSuite *ChannelBuilderTestSuite) TestBuilderRegisterL1Block() { +func TestBuilderRegisterL1Block(t *testing.T) { + channelConfig := defaultTestChannelConfig + // Construct the channel builder - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Assert params modified in RegisterL1Block - testSuite.Equal(uint64(1), testSuite.channelConfig.MaxChannelDuration) - testSuite.Equal(uint64(0), cb.timeout) + require.Equal(t, uint64(1), channelConfig.MaxChannelDuration) + require.Equal(t, uint64(0), cb.timeout) // Register a new L1 block cb.RegisterL1Block(uint64(100)) // Assert params modified in RegisterL1Block - testSuite.Equal(uint64(1), testSuite.channelConfig.MaxChannelDuration) - testSuite.Equal(uint64(101), cb.timeout) + require.Equal(t, uint64(1), channelConfig.MaxChannelDuration) + require.Equal(t, uint64(101), cb.timeout) } // TestBuilderRegisterL1BlockZeroMaxChannelDuration tests the RegisterL1Block function -func (testSuite *ChannelBuilderTestSuite) TestBuilderRegisterL1BlockZeroMaxChannelDuration() { +func TestBuilderRegisterL1BlockZeroMaxChannelDuration(t *testing.T) { + channelConfig := defaultTestChannelConfig + // Set the max channel duration to 0 - testSuite.channelConfig.MaxChannelDuration = 0 + channelConfig.MaxChannelDuration = 0 // Construct the channel builder - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Assert params modified in RegisterL1Block - testSuite.Equal(uint64(0), testSuite.channelConfig.MaxChannelDuration) - testSuite.Equal(uint64(0), cb.timeout) + require.Equal(t, uint64(0), channelConfig.MaxChannelDuration) + require.Equal(t, uint64(0), cb.timeout) // Register a new L1 block cb.RegisterL1Block(uint64(100)) // Since the max channel duration is set to 0, // the L1 block register should not update the timeout - testSuite.Equal(uint64(0), testSuite.channelConfig.MaxChannelDuration) - testSuite.Equal(uint64(0), cb.timeout) + require.Equal(t, uint64(0), channelConfig.MaxChannelDuration) + require.Equal(t, uint64(0), cb.timeout) } // TestFramePublished tests the FramePublished function -func (testSuite *ChannelBuilderTestSuite) TestFramePublished() { +func TestFramePublished(t *testing.T) { + channelConfig := defaultTestChannelConfig + // Construct the channel builder - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) // Let's say the block number is fed in as 100 // and the channel timeout is 1000 @@ -299,28 +298,5 @@ func (testSuite *ChannelBuilderTestSuite) TestFramePublished() { cb.FramePublished(l1BlockNum) // Now the timeout will be 1000 - testSuite.Equal(uint64(1000), cb.timeout) -} - -// TestFramePublishedUnderflows tests the FramePublished function -// -// Realistically, this will not happen because the [ChannelTimeout] is _known_ -// to be greater than the [SubSafetyMargin] when the ChannelConfig is configured -// via cli flags / environment variables. -func (testSuite *ChannelBuilderTestSuite) TestFramePublishedUnderflows() { - // Construct the channel builder - cb, err := newChannelBuilder(testSuite.channelConfig) - testSuite.NoError(err) - - // Let's say the block number is fed in as 0 - // and the channel timeout is < the sub safety margin - l1BlockNum := uint64(0) - cb.cfg.ChannelTimeout = uint64(1) - cb.cfg.SubSafetyMargin = uint64(2) - - // Then the frame published will underflow the timeout - cb.FramePublished(l1BlockNum) - - // Now the timeout will be the max uint64 - testSuite.Equal(uint64(0xffffffffffffffff), cb.timeout) + require.Equal(t, uint64(1000), cb.timeout) } From 219eaf6a908a33fe3f1cdcb8f50307e5c9fd4df2 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 11:03:16 -0400 Subject: [PATCH 009/404] table-driven tests for the input threshold function --- op-batcher/batcher/channel_config_test.go | 113 +++++++++++++++++----- 1 file changed, 88 insertions(+), 25 deletions(-) diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index 1682a8561..cce15a543 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -7,33 +7,96 @@ import ( "github.com/stretchr/testify/require" ) -// TestInputThreshold tests the [ChannelConfig.InputThreshold] function. +// TestInputThreshold tests the [ChannelConfig.InputThreshold] +// function using a table-driven testing approach. func TestInputThreshold(t *testing.T) { - // Construct an empty channel config - config := batcher.ChannelConfig{ - SeqWindowSize: 15, - ChannelTimeout: 40, - MaxChannelDuration: 1, - SubSafetyMargin: 4, - MaxFrameSize: 120000, - TargetFrameSize: 100000, - TargetNumFrames: 1, - ApproxComprRatio: 0.4, + type testInput struct { + TargetFrameSize uint64 + TargetNumFrames int + ApproxComprRatio float64 + } + type test struct { + input testInput + want uint64 } - // The input threshold is calculated as: (targetNumFrames * targetFrameSize) / approxComprRatio - // Here we see that 100,000 / 0.4 = 100,000 * 2.5 = 250,000 - inputThreshold := config.InputThreshold() - require.Equal(t, uint64(250_000), inputThreshold) - - // Set the approximate compression ratio to 0 - // Logically, this represents infinite compression, - // so there is no threshold on the size of the input. - // In practice, this should never be set to 0. - config.ApproxComprRatio = 0 + // Construct test cases that test the boundary conditions + tests := []test{ + { + input: testInput{ + TargetFrameSize: 1, + TargetNumFrames: 1, + ApproxComprRatio: 0.4, + }, + want: 2, + }, + { + input: testInput{ + TargetFrameSize: 1, + TargetNumFrames: 1, + ApproxComprRatio: 1, + }, + want: 1, + }, + { + input: testInput{ + TargetFrameSize: 1, + TargetNumFrames: 1, + ApproxComprRatio: 2, + }, + want: 0, + }, + { + input: testInput{ + TargetFrameSize: 100000, + TargetNumFrames: 1, + ApproxComprRatio: 0.4, + }, + want: 250_000, + }, + { + input: testInput{ + TargetFrameSize: 1, + TargetNumFrames: 100000, + ApproxComprRatio: 0.4, + }, + want: 250_000, + }, + { + input: testInput{ + TargetFrameSize: 100000, + TargetNumFrames: 100000, + ApproxComprRatio: 0.4, + }, + want: 25_000_000_000, + }, + // A compression ratio of 0 means there is no input threshold + { + input: testInput{ + TargetFrameSize: 100000, + TargetNumFrames: 100000, + ApproxComprRatio: 0, + }, + want: uint64(0xffffffffffffffff), + }, + { + input: testInput{ + TargetFrameSize: 0, + TargetNumFrames: 0, + ApproxComprRatio: 0, + }, + want: 0, + }, + } - // The input threshold will overflow to the max uint64 value - receivedThreshold := config.InputThreshold() - max := config.TargetNumFrames * int(config.TargetFrameSize) - require.True(t, receivedThreshold > uint64(max)) + // Validate each test case + for _, tt := range tests { + config := batcher.ChannelConfig{ + TargetFrameSize: tt.input.TargetFrameSize, + TargetNumFrames: tt.input.TargetNumFrames, + ApproxComprRatio: tt.input.ApproxComprRatio, + } + got := config.InputThreshold() + require.Equal(t, tt.want, got) + } } From 59c146651c856ea91c99b7922e16ad6e2079a8dc Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 11:15:23 -0400 Subject: [PATCH 010/404] remove maxsized test --- op-batcher/batcher/channel_config_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index cce15a543..b775c2f7c 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -70,14 +70,13 @@ func TestInputThreshold(t *testing.T) { }, want: 25_000_000_000, }, - // A compression ratio of 0 means there is no input threshold { input: testInput{ - TargetFrameSize: 100000, - TargetNumFrames: 100000, - ApproxComprRatio: 0, + TargetFrameSize: 1, + TargetNumFrames: 1, + ApproxComprRatio: 0.000001, }, - want: uint64(0xffffffffffffffff), + want: 1_000_000, }, { input: testInput{ From b803cfd246b6db6280154a8b13a2db7d1fef1179 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 11:27:06 -0400 Subject: [PATCH 011/404] dynamic assertions :sparkles: --- op-batcher/batcher/channel_config_test.go | 40 ++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index b775c2f7c..18533ae2f 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -1,6 +1,7 @@ package batcher_test import ( + "math" "testing" "github.com/ethereum-optimism/optimism/op-batcher/batcher" @@ -16,8 +17,8 @@ func TestInputThreshold(t *testing.T) { ApproxComprRatio float64 } type test struct { - input testInput - want uint64 + input testInput + assertion func(uint64) } // Construct test cases that test the boundary conditions @@ -28,7 +29,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 1, ApproxComprRatio: 0.4, }, - want: 2, + assertion: func(output uint64) { + require.Equal(t, uint64(2), output) + }, }, { input: testInput{ @@ -36,7 +39,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 1, ApproxComprRatio: 1, }, - want: 1, + assertion: func(output uint64) { + require.Equal(t, uint64(1), output) + }, }, { input: testInput{ @@ -44,7 +49,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 1, ApproxComprRatio: 2, }, - want: 0, + assertion: func(output uint64) { + require.Equal(t, uint64(0), output) + }, }, { input: testInput{ @@ -52,7 +59,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 1, ApproxComprRatio: 0.4, }, - want: 250_000, + assertion: func(output uint64) { + require.Equal(t, uint64(250_000), output) + }, }, { input: testInput{ @@ -60,7 +69,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 100000, ApproxComprRatio: 0.4, }, - want: 250_000, + assertion: func(output uint64) { + require.Equal(t, uint64(250_000), output) + }, }, { input: testInput{ @@ -68,7 +79,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 100000, ApproxComprRatio: 0.4, }, - want: 25_000_000_000, + assertion: func(output uint64) { + require.Equal(t, uint64(25_000_000_000), output) + }, }, { input: testInput{ @@ -76,7 +89,9 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 1, ApproxComprRatio: 0.000001, }, - want: 1_000_000, + assertion: func(output uint64) { + require.Equal(t, uint64(1_000_000), output) + }, }, { input: testInput{ @@ -84,7 +99,10 @@ func TestInputThreshold(t *testing.T) { TargetNumFrames: 0, ApproxComprRatio: 0, }, - want: 0, + assertion: func(output uint64) { + // Need to allow for NaN depending on the machine architecture + require.True(t, output == uint64(0) || output == uint64(math.NaN())) + }, }, } @@ -96,6 +114,6 @@ func TestInputThreshold(t *testing.T) { ApproxComprRatio: tt.input.ApproxComprRatio, } got := config.InputThreshold() - require.Equal(t, tt.want, got) + tt.assertion(got) } } From afa252816deef87627a4bdaf1066b77eed3ac1c8 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 15:32:28 -0400 Subject: [PATCH 012/404] max frame index test :test_tube: --- op-batcher/batcher/channel_builder_test.go | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index e19239935..0d189a9f6 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -2,6 +2,8 @@ package batcher import ( "bytes" + "crypto/rand" + "math" "math/big" "testing" @@ -46,6 +48,36 @@ func addNonsenseBlock(cb *channelBuilder) error { return err } +// buildTooLargeRlpEncodedBlockBatch is a helper function that builds a batch +// of blocks that are too large to be added to a channel. +func buildTooLargeRlpEncodedBlockBatch(cb *channelBuilder) error { + // Construct a block with way too many txs + lBlock := types.NewBlock(&types.Header{ + BaseFee: big.NewInt(10), + Difficulty: common.Big0, + Number: big.NewInt(100), + }, nil, nil, nil, trie.NewStackTrie(nil)) + l1InfoTx, _ := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) + txs := []*types.Transaction{types.NewTx(l1InfoTx)} + for i := 0; i < 500_000; i++ { + txData := make([]byte, 32) + _, _ = rand.Read(txData) + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), txData) + txs = append(txs, tx) + } + block := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, txs, nil, nil, trie.NewStackTrie(nil)) + + // Try to add the block to the channel builder + // This should fail since the block is too large + // When a batch is constructed from the block and + // then rlp encoded in the channel out, the size + // will exceed [derive.MaxRLPBytesPerChannel] + err := cb.AddBlock(block) + return err +} + // TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame func TestBuilderNextFrame(t *testing.T) { channelConfig := defaultTestChannelConfig @@ -153,6 +185,67 @@ func TestOutputFrames(t *testing.T) { require.Equal(t, 0, readyBytes) } +// TestMaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames] +// function errors when the max RLP bytes per channel is reached. +func TestMaxRLPBytesPerChannel(t *testing.T) { + channelConfig := defaultTestChannelConfig + + // Lower the max frame size so that we can test + channelConfig.MaxFrameSize = 2 + + // Construct the channel builder + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + require.False(t, cb.IsFull()) + require.Equal(t, 0, cb.NumFrames()) + + // Add a block that overflows the [ChannelOut] + err = buildTooLargeRlpEncodedBlockBatch(cb) + require.ErrorIsf(t, err, derive.ErrTooManyRLPBytes, "err: %v", err) +} + +// TestOutputFramesMaxFrameIndex tests the [channelBuilder.OutputFrames] +// function errors when the max frame index is reached. +func TestOutputFramesMaxFrameIndex(t *testing.T) { + channelConfig := defaultTestChannelConfig + + // Lower the max frame size so that we can test + channelConfig.MaxFrameSize = 1 + channelConfig.TargetNumFrames = math.MaxInt + channelConfig.TargetFrameSize = 1 // math.MaxUint64 + channelConfig.ApproxComprRatio = 0 + + // Continuously add blocks until the max frame index is reached + // This should cause the [channelBuilder.OutputFrames] function + // to error + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + require.False(t, cb.IsFull()) + require.Equal(t, 0, cb.NumFrames()) + for { + lBlock := types.NewBlock(&types.Header{ + BaseFee: common.Big0, + Difficulty: common.Big0, + Number: common.Big0, + }, nil, nil, nil, trie.NewStackTrie(nil)) + l1InfoTx, _ := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) + txs := []*types.Transaction{types.NewTx(l1InfoTx)} + a := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, txs, nil, nil, trie.NewStackTrie(nil)) + err = cb.AddBlock(a) + if cb.IsFull() { + fullErr := cb.FullErr() + require.ErrorIsf(t, fullErr, ErrMaxFrameIndex, "err: %v", fullErr) + break + } + require.NoError(t, err) + _ = cb.OutputFrames() + // Flushing so we can construct new frames + _ = cb.co.Flush() + } +} + // TestBuilderAddBlock tests the AddBlock function func TestBuilderAddBlock(t *testing.T) { channelConfig := defaultTestChannelConfig From 081b40b24e3b404e2e49ecf1400e82ada1db5c5d Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 16:23:53 -0400 Subject: [PATCH 013/404] err max duration reached :test_tube: --- op-batcher/batcher/channel_builder_test.go | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 0d189a9f6..4c44cdd6d 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -78,6 +78,86 @@ func buildTooLargeRlpEncodedBlockBatch(cb *channelBuilder) error { return err } +// FuzzDurationTimeoutZeroMaxChannelDuration ensures that when whenever the MaxChannelDuration +// is set to 0, the channel builder cannot have a duration timeout. +func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) { + for i := range [10]int{} { + f.Add(uint64(i)) + } + f.Fuzz(func(t *testing.T, l1BlockNum uint64) { + channelConfig := defaultTestChannelConfig + channelConfig.MaxChannelDuration = 0 + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + cb.timeout = 0 + cb.updateDurationTimeout(l1BlockNum) + require.False(t, cb.TimedOut(l1BlockNum)) + }) +} + +// FuzzDuration ensures that when whenever the MaxChannelDuration +// is not set to 0, the channel builder will always have a duration timeout +// as long as the channel builder's timeout is set to 0. +func FuzzDuration(f *testing.F) { + for i := range [10]int{} { + f.Add(uint64(i), uint64(i)) + } + f.Fuzz(func(t *testing.T, l1BlockNum uint64, maxChannelDuration uint64) { + if maxChannelDuration == 0 { + t.Skip("Max channel duration cannot be 0") + } + + // Create the channel builder + channelConfig := defaultTestChannelConfig + channelConfig.MaxChannelDuration = maxChannelDuration + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + + // Whenever the timeout is set to 0, the channel builder should have a duration timeout + cb.timeout = 0 + cb.updateDurationTimeout(l1BlockNum) + cb.checkTimeout(l1BlockNum + maxChannelDuration) + require.ErrorIsf(t, cb.FullErr(), ErrMaxDurationReached, "Max channel duration should be reached") + }) +} + +// FuzzDurationTimeout ensures that when whenever the MaxChannelDuration +// is not set to 0, the channel builder will always have a duration timeout +// as long as the channel builder's timeout is greater than the target block number. +func FuzzDurationTimeout(f *testing.F) { + // Set multiple seeds in case fuzzing isn't explicitly used + for i := range [10]int{} { + f.Add(uint64(i), uint64(i), uint64(i)) + } + f.Fuzz(func(t *testing.T, l1BlockNum uint64, maxChannelDuration uint64, timeout uint64) { + if maxChannelDuration == 0 { + t.Skip("Max channel duration cannot be 0") + } + + // Create the channel builder + channelConfig := defaultTestChannelConfig + channelConfig.MaxChannelDuration = maxChannelDuration + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + + // Whenever the timeout is greater than the l1BlockNum, + // the channel builder should have a duration timeout + cb.timeout = timeout + cb.updateDurationTimeout(l1BlockNum) + if timeout > l1BlockNum+maxChannelDuration { + // Notice: we cannot call this outside of the if statement + // because it would put the channel builder in an invalid state. + // That is, where the channel builder has a value set for the timeout + // with no timeoutReason. This subsequently causes a panic when + // a nil timeoutReason is used as an error (eg when calling FullErr). + cb.checkTimeout(l1BlockNum + maxChannelDuration) + require.ErrorIsf(t, cb.FullErr(), ErrMaxDurationReached, "Max channel duration should be reached") + } else { + require.NoError(t, cb.FullErr()) + } + }) +} + // TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame func TestBuilderNextFrame(t *testing.T) { channelConfig := defaultTestChannelConfig From 42c3a29eb44d6b2cbdecdf57de9d06e41b2433be Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 17:15:11 -0400 Subject: [PATCH 014/404] timeout close --- op-batcher/batcher/channel_builder_test.go | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 4c44cdd6d..df9725489 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -158,6 +158,61 @@ func FuzzDurationTimeout(f *testing.F) { }) } +// FuzzChannelTimeoutClose ensures that the channel builder has a [ErrChannelTimeoutClose] +// as long as the timeout constraint is met and the builder's timeout is greater than +// the calculated timeout +func FuzzChannelTimeoutClose(f *testing.F) { + // Set multiple seeds in case fuzzing isn't explicitly used + for i := range [10]int{} { + f.Add(uint64(i), uint64(i), uint64(i), uint64(i*5)) + } + f.Fuzz(func(t *testing.T, l1BlockNum uint64, channelTimeout uint64, subSafetyMargin uint64, timeout uint64) { + // Create the channel builder + channelConfig := defaultTestChannelConfig + channelConfig.ChannelTimeout = channelTimeout + channelConfig.SubSafetyMargin = subSafetyMargin + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + + // Check the timeout + cb.timeout = timeout + cb.FramePublished(l1BlockNum) + calculatedTimeout := l1BlockNum + channelTimeout - subSafetyMargin + if timeout > calculatedTimeout { + cb.checkTimeout(calculatedTimeout) + require.ErrorIsf(t, cb.FullErr(), ErrChannelTimeoutClose, "Channel timeout close should be reached") + } else { + require.NoError(t, cb.FullErr()) + } + }) +} + +// FuzzChannelTimeoutCloseZeroTimeout ensures that the channel builder has a [ErrChannelTimeoutClose] +// as long as the timeout constraint is met and the builder's timeout is set to zero. +func FuzzChannelTimeoutCloseZeroTimeout(f *testing.F) { + // Set multiple seeds in case fuzzing isn't explicitly used + for i := range [10]int{} { + f.Add(uint64(i), uint64(i), uint64(i)) + } + f.Fuzz(func(t *testing.T, l1BlockNum uint64, channelTimeout uint64, subSafetyMargin uint64) { + // Create the channel builder + channelConfig := defaultTestChannelConfig + channelConfig.ChannelTimeout = channelTimeout + channelConfig.SubSafetyMargin = subSafetyMargin + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + + // Check the timeout + cb.timeout = 0 + cb.FramePublished(l1BlockNum) + calculatedTimeout := l1BlockNum + channelTimeout - subSafetyMargin + cb.checkTimeout(calculatedTimeout) + if cb.timeout != 0 { + require.ErrorIsf(t, cb.FullErr(), ErrChannelTimeoutClose, "Channel timeout close should be reached") + } + }) +} + // TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame func TestBuilderNextFrame(t *testing.T) { channelConfig := defaultTestChannelConfig From 2505714f9ff00c5c64662573d2bcf2fb99e6a171 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 17:19:43 -0400 Subject: [PATCH 015/404] upstream sync :recycle: --- op-batcher/batcher/channel_builder_test.go | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index df9725489..713516b97 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -231,15 +231,22 @@ func TestBuilderNextFrame(t *testing.T) { // Push one frame into to the channel builder expectedTx := txID{chID: co.ID(), frameNumber: fn} expectedBytes := buf.Bytes() - cb.PushFrame(expectedTx, expectedBytes) + frameData := &frameData{ + id: frameID{ + chID: co.ID(), + frameNumber: fn, + }, + data: expectedBytes, + } + cb.PushFrame(*frameData) // There should only be 1 frame in the channel builder require.Equal(t, 1, cb.NumFrames()) // We should be able to increment to the next frame - constructedTx, constructedBytes := cb.NextFrame() - require.Equal(t, expectedTx, constructedTx) - require.Equal(t, expectedBytes, constructedBytes) + constructedFrame := cb.NextFrame() + require.Equal(t, expectedTx, constructedFrame.id) + require.Equal(t, expectedBytes, constructedFrame.data) require.Equal(t, 0, cb.NumFrames()) // The next call should panic since the length of frames is 0 @@ -265,8 +272,14 @@ func TestBuilderWrongFramePanic(t *testing.T) { // The frame push should panic since we constructed a new channel out // so the channel out id won't match require.PanicsWithValue(t, "wrong channel", func() { - tx := txID{chID: co.ID(), frameNumber: fn} - cb.PushFrame(tx, buf.Bytes()) + frame := &frameData{ + id: frameID{ + chID: co.ID(), + frameNumber: fn, + }, + data: buf.Bytes(), + } + cb.PushFrame(*frame) }) } From 389e97fe736ec781481dc5714e254e904a62efd6 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 17:24:22 -0400 Subject: [PATCH 016/404] err seq window close --- op-batcher/batcher/channel_builder_test.go | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 713516b97..7f0c73db7 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum/go-ethereum/common" @@ -213,6 +214,69 @@ func FuzzChannelTimeoutCloseZeroTimeout(f *testing.F) { }) } +// FuzzSeqWindowClose ensures that the channel builder has a [ErrSeqWindowClose] +// as long as the timeout constraint is met and the builder's timeout is greater than +// the calculated timeout +func FuzzSeqWindowClose(f *testing.F) { + // Set multiple seeds in case fuzzing isn't explicitly used + for i := range [10]int{} { + f.Add(uint64(i), uint64(i), uint64(i), uint64(i*5)) + } + f.Fuzz(func(t *testing.T, epochNum uint64, seqWindowSize uint64, subSafetyMargin uint64, timeout uint64) { + // Create the channel builder + channelConfig := defaultTestChannelConfig + channelConfig.SeqWindowSize = seqWindowSize + channelConfig.SubSafetyMargin = subSafetyMargin + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + + // Check the timeout + cb.timeout = timeout + cb.updateSwTimeout(&derive.BatchData{ + BatchV1: derive.BatchV1{ + EpochNum: rollup.Epoch(epochNum), + }, + }) + calculatedTimeout := epochNum + seqWindowSize - subSafetyMargin + if timeout > calculatedTimeout { + cb.checkTimeout(calculatedTimeout) + require.ErrorIsf(t, cb.FullErr(), ErrSeqWindowClose, "Sequence window close should be reached") + } else { + require.NoError(t, cb.FullErr()) + } + }) +} + +// FuzzSeqWindowCloseZeroTimeout ensures that the channel builder has a [ErrSeqWindowClose] +// as long as the timeout constraint is met and the builder's timeout is set to zero. +func FuzzSeqWindowCloseZeroTimeout(f *testing.F) { + // Set multiple seeds in case fuzzing isn't explicitly used + for i := range [10]int{} { + f.Add(uint64(i), uint64(i), uint64(i)) + } + f.Fuzz(func(t *testing.T, epochNum uint64, seqWindowSize uint64, subSafetyMargin uint64) { + // Create the channel builder + channelConfig := defaultTestChannelConfig + channelConfig.SeqWindowSize = seqWindowSize + channelConfig.SubSafetyMargin = subSafetyMargin + cb, err := newChannelBuilder(channelConfig) + require.NoError(t, err) + + // Check the timeout + cb.timeout = 0 + cb.updateSwTimeout(&derive.BatchData{ + BatchV1: derive.BatchV1{ + EpochNum: rollup.Epoch(epochNum), + }, + }) + calculatedTimeout := epochNum + seqWindowSize - subSafetyMargin + cb.checkTimeout(calculatedTimeout) + if cb.timeout != 0 { + require.ErrorIsf(t, cb.FullErr(), ErrSeqWindowClose, "Sequence window close should be reached") + } + }) +} + // TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame func TestBuilderNextFrame(t *testing.T) { channelConfig := defaultTestChannelConfig From 493193b574b301c13c46003492386a946354aab2 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 21:01:40 -0400 Subject: [PATCH 017/404] remove formatted error checks --- op-batcher/batcher/channel_builder_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 7f0c73db7..f5300a597 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -118,7 +118,7 @@ func FuzzDuration(f *testing.F) { cb.timeout = 0 cb.updateDurationTimeout(l1BlockNum) cb.checkTimeout(l1BlockNum + maxChannelDuration) - require.ErrorIsf(t, cb.FullErr(), ErrMaxDurationReached, "Max channel duration should be reached") + require.ErrorIs(t, cb.FullErr(), ErrMaxDurationReached) }) } @@ -152,7 +152,7 @@ func FuzzDurationTimeout(f *testing.F) { // with no timeoutReason. This subsequently causes a panic when // a nil timeoutReason is used as an error (eg when calling FullErr). cb.checkTimeout(l1BlockNum + maxChannelDuration) - require.ErrorIsf(t, cb.FullErr(), ErrMaxDurationReached, "Max channel duration should be reached") + require.ErrorIs(t, cb.FullErr(), ErrMaxDurationReached) } else { require.NoError(t, cb.FullErr()) } @@ -181,7 +181,7 @@ func FuzzChannelTimeoutClose(f *testing.F) { calculatedTimeout := l1BlockNum + channelTimeout - subSafetyMargin if timeout > calculatedTimeout { cb.checkTimeout(calculatedTimeout) - require.ErrorIsf(t, cb.FullErr(), ErrChannelTimeoutClose, "Channel timeout close should be reached") + require.ErrorIs(t, cb.FullErr(), ErrChannelTimeoutClose) } else { require.NoError(t, cb.FullErr()) } @@ -209,7 +209,7 @@ func FuzzChannelTimeoutCloseZeroTimeout(f *testing.F) { calculatedTimeout := l1BlockNum + channelTimeout - subSafetyMargin cb.checkTimeout(calculatedTimeout) if cb.timeout != 0 { - require.ErrorIsf(t, cb.FullErr(), ErrChannelTimeoutClose, "Channel timeout close should be reached") + require.ErrorIs(t, cb.FullErr(), ErrChannelTimeoutClose) } }) } @@ -240,7 +240,7 @@ func FuzzSeqWindowClose(f *testing.F) { calculatedTimeout := epochNum + seqWindowSize - subSafetyMargin if timeout > calculatedTimeout { cb.checkTimeout(calculatedTimeout) - require.ErrorIsf(t, cb.FullErr(), ErrSeqWindowClose, "Sequence window close should be reached") + require.ErrorIs(t, cb.FullErr(), ErrSeqWindowClose) } else { require.NoError(t, cb.FullErr()) } @@ -272,7 +272,7 @@ func FuzzSeqWindowCloseZeroTimeout(f *testing.F) { calculatedTimeout := epochNum + seqWindowSize - subSafetyMargin cb.checkTimeout(calculatedTimeout) if cb.timeout != 0 { - require.ErrorIsf(t, cb.FullErr(), ErrSeqWindowClose, "Sequence window close should be reached") + require.ErrorIs(t, cb.FullErr(), ErrSeqWindowClose) } }) } @@ -413,7 +413,7 @@ func TestMaxRLPBytesPerChannel(t *testing.T) { // Add a block that overflows the [ChannelOut] err = buildTooLargeRlpEncodedBlockBatch(cb) - require.ErrorIsf(t, err, derive.ErrTooManyRLPBytes, "err: %v", err) + require.ErrorIs(t, err, derive.ErrTooManyRLPBytes) } // TestOutputFramesMaxFrameIndex tests the [channelBuilder.OutputFrames] @@ -448,7 +448,7 @@ func TestOutputFramesMaxFrameIndex(t *testing.T) { err = cb.AddBlock(a) if cb.IsFull() { fullErr := cb.FullErr() - require.ErrorIsf(t, fullErr, ErrMaxFrameIndex, "err: %v", fullErr) + require.ErrorIs(t, fullErr, ErrMaxFrameIndex) break } require.NoError(t, err) From ccba651949472d6d8582eb32d4365b72e2aa4faf Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 21:07:39 -0400 Subject: [PATCH 018/404] add explicit target num frames > 1 test --- op-batcher/batcher/channel_config_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/op-batcher/batcher/channel_config_test.go b/op-batcher/batcher/channel_config_test.go index 18533ae2f..1b2cb202d 100644 --- a/op-batcher/batcher/channel_config_test.go +++ b/op-batcher/batcher/channel_config_test.go @@ -33,6 +33,16 @@ func TestInputThreshold(t *testing.T) { require.Equal(t, uint64(2), output) }, }, + { + input: testInput{ + TargetFrameSize: 1, + TargetNumFrames: 100000, + ApproxComprRatio: 0.4, + }, + assertion: func(output uint64) { + require.Equal(t, uint64(250_000), output) + }, + }, { input: testInput{ TargetFrameSize: 1, From f7e4cb6040388bf61b2fe9cf4a4ccb765cd200db Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 21:09:11 -0400 Subject: [PATCH 019/404] remove explicit reference on instantiation for frameData --- op-batcher/batcher/channel_builder_test.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index f5300a597..74ec5bc8e 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -295,14 +295,14 @@ func TestBuilderNextFrame(t *testing.T) { // Push one frame into to the channel builder expectedTx := txID{chID: co.ID(), frameNumber: fn} expectedBytes := buf.Bytes() - frameData := &frameData{ + frameData := frameData{ id: frameID{ chID: co.ID(), frameNumber: fn, }, data: expectedBytes, } - cb.PushFrame(*frameData) + cb.PushFrame(frameData) // There should only be 1 frame in the channel builder require.Equal(t, 1, cb.NumFrames()) @@ -336,22 +336,20 @@ func TestBuilderWrongFramePanic(t *testing.T) { // The frame push should panic since we constructed a new channel out // so the channel out id won't match require.PanicsWithValue(t, "wrong channel", func() { - frame := &frameData{ + frame := frameData{ id: frameID{ chID: co.ID(), frameNumber: fn, }, data: buf.Bytes(), } - cb.PushFrame(*frame) + cb.PushFrame(frame) }) } // TestOutputFrames tests the OutputFrames function func TestOutputFrames(t *testing.T) { channelConfig := defaultTestChannelConfig - - // Lower the max frame size so that we can test channelConfig.MaxFrameSize = 2 // Construct the channel builder @@ -401,8 +399,6 @@ func TestOutputFrames(t *testing.T) { // function errors when the max RLP bytes per channel is reached. func TestMaxRLPBytesPerChannel(t *testing.T) { channelConfig := defaultTestChannelConfig - - // Lower the max frame size so that we can test channelConfig.MaxFrameSize = 2 // Construct the channel builder @@ -420,8 +416,6 @@ func TestMaxRLPBytesPerChannel(t *testing.T) { // function errors when the max frame index is reached. func TestOutputFramesMaxFrameIndex(t *testing.T) { channelConfig := defaultTestChannelConfig - - // Lower the max frame size so that we can test channelConfig.MaxFrameSize = 1 channelConfig.TargetNumFrames = math.MaxInt channelConfig.TargetFrameSize = 1 // math.MaxUint64 From 0781a082df8a034751cc8389b6f298f19536db32 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 21:38:42 -0400 Subject: [PATCH 020/404] add fuzzing to the batcher makefile as a stop-gap solution --- op-batcher/Makefile | 12 ++++++++++- op-batcher/batcher/channel_builder_test.go | 24 +++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/op-batcher/Makefile b/op-batcher/Makefile index 93a0bd587..3235b3b77 100644 --- a/op-batcher/Makefile +++ b/op-batcher/Makefile @@ -19,8 +19,18 @@ test: lint: golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" +fuzz: + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationZero ./batcher + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationTimeoutMaxChannelDuration ./batcher + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationTimeoutZeroMaxChannelDuration ./batcher + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelCloseTimeout ./batcher + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelZeroCloseTimeout ./batcher + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzSeqWindowClose ./batcher + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzSeqWindowZeroTimeoutClose ./batcher + .PHONY: \ op-batcher \ clean \ test \ - lint + lint \ + fuzz diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 74ec5bc8e..2e657f2c4 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -96,10 +96,10 @@ func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) { }) } -// FuzzDuration ensures that when whenever the MaxChannelDuration +// FuzzDurationZero ensures that when whenever the MaxChannelDuration // is not set to 0, the channel builder will always have a duration timeout // as long as the channel builder's timeout is set to 0. -func FuzzDuration(f *testing.F) { +func FuzzDurationZero(f *testing.F) { for i := range [10]int{} { f.Add(uint64(i), uint64(i)) } @@ -122,10 +122,10 @@ func FuzzDuration(f *testing.F) { }) } -// FuzzDurationTimeout ensures that when whenever the MaxChannelDuration +// FuzzDurationTimeoutMaxChannelDuration ensures that when whenever the MaxChannelDuration // is not set to 0, the channel builder will always have a duration timeout // as long as the channel builder's timeout is greater than the target block number. -func FuzzDurationTimeout(f *testing.F) { +func FuzzDurationTimeoutMaxChannelDuration(f *testing.F) { // Set multiple seeds in case fuzzing isn't explicitly used for i := range [10]int{} { f.Add(uint64(i), uint64(i), uint64(i)) @@ -159,10 +159,10 @@ func FuzzDurationTimeout(f *testing.F) { }) } -// FuzzChannelTimeoutClose ensures that the channel builder has a [ErrChannelTimeoutClose] +// FuzzChannelCloseTimeout ensures that the channel builder has a [ErrChannelTimeoutClose] // as long as the timeout constraint is met and the builder's timeout is greater than // the calculated timeout -func FuzzChannelTimeoutClose(f *testing.F) { +func FuzzChannelCloseTimeout(f *testing.F) { // Set multiple seeds in case fuzzing isn't explicitly used for i := range [10]int{} { f.Add(uint64(i), uint64(i), uint64(i), uint64(i*5)) @@ -179,7 +179,7 @@ func FuzzChannelTimeoutClose(f *testing.F) { cb.timeout = timeout cb.FramePublished(l1BlockNum) calculatedTimeout := l1BlockNum + channelTimeout - subSafetyMargin - if timeout > calculatedTimeout { + if timeout > calculatedTimeout && calculatedTimeout != 0 { cb.checkTimeout(calculatedTimeout) require.ErrorIs(t, cb.FullErr(), ErrChannelTimeoutClose) } else { @@ -188,9 +188,9 @@ func FuzzChannelTimeoutClose(f *testing.F) { }) } -// FuzzChannelTimeoutCloseZeroTimeout ensures that the channel builder has a [ErrChannelTimeoutClose] +// FuzzChannelZeroCloseTimeout ensures that the channel builder has a [ErrChannelTimeoutClose] // as long as the timeout constraint is met and the builder's timeout is set to zero. -func FuzzChannelTimeoutCloseZeroTimeout(f *testing.F) { +func FuzzChannelZeroCloseTimeout(f *testing.F) { // Set multiple seeds in case fuzzing isn't explicitly used for i := range [10]int{} { f.Add(uint64(i), uint64(i), uint64(i)) @@ -238,7 +238,7 @@ func FuzzSeqWindowClose(f *testing.F) { }, }) calculatedTimeout := epochNum + seqWindowSize - subSafetyMargin - if timeout > calculatedTimeout { + if timeout > calculatedTimeout && calculatedTimeout != 0 { cb.checkTimeout(calculatedTimeout) require.ErrorIs(t, cb.FullErr(), ErrSeqWindowClose) } else { @@ -247,9 +247,9 @@ func FuzzSeqWindowClose(f *testing.F) { }) } -// FuzzSeqWindowCloseZeroTimeout ensures that the channel builder has a [ErrSeqWindowClose] +// FuzzSeqWindowZeroTimeoutClose ensures that the channel builder has a [ErrSeqWindowClose] // as long as the timeout constraint is met and the builder's timeout is set to zero. -func FuzzSeqWindowCloseZeroTimeout(f *testing.F) { +func FuzzSeqWindowZeroTimeoutClose(f *testing.F) { // Set multiple seeds in case fuzzing isn't explicitly used for i := range [10]int{} { f.Add(uint64(i), uint64(i), uint64(i)) From 6294e02e74b353b7add2b9e29d4fc89ee2ffb001 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 14 Mar 2023 22:09:00 -0400 Subject: [PATCH 021/404] nextTxData tests :test_tube: --- op-batcher/batcher/channel_manager_test.go | 53 +++++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 41e1b52c3..4296f35ee 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -1,4 +1,4 @@ -package batcher_test +package batcher import ( "io" @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" derivetest "github.com/ethereum-optimism/optimism/op-node/rollup/derive/test" @@ -23,7 +22,7 @@ import ( // detects a reorg when it has cached L1 blocks. func TestChannelManagerReturnsErrReorg(t *testing.T) { log := testlog.Logger(t, log.LvlCrit) - m := batcher.NewChannelManager(log, batcher.ChannelConfig{}) + m := NewChannelManager(log, ChannelConfig{}) a := types.NewBlock(&types.Header{ Number: big.NewInt(0), @@ -48,14 +47,14 @@ func TestChannelManagerReturnsErrReorg(t *testing.T) { err = m.AddL2Block(c) require.NoError(t, err) err = m.AddL2Block(x) - require.ErrorIs(t, err, batcher.ErrReorg) + require.ErrorIs(t, err, ErrReorg) } // TestChannelManagerReturnsErrReorgWhenDrained ensures that the channel manager // detects a reorg even if it does not have any blocks inside it. func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) { log := testlog.Logger(t, log.LvlCrit) - m := batcher.NewChannelManager(log, batcher.ChannelConfig{ + m := NewChannelManager(log, ChannelConfig{ TargetFrameSize: 0, MaxFrameSize: 120_000, ApproxComprRatio: 1.0, @@ -86,14 +85,54 @@ func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) { require.ErrorIs(t, err, io.EOF) err = m.AddL2Block(x) - require.ErrorIs(t, err, batcher.ErrReorg) + require.ErrorIs(t, err, ErrReorg) +} + +// TestChannelManagerNextTxData checks the nextTxData function. +func TestChannelManagerNextTxData(t *testing.T) { + log := testlog.Logger(t, log.LvlCrit) + m := NewChannelManager(log, ChannelConfig{}) + + // Nil pending channel should return EOF + returnedTxData, err := m.nextTxData() + require.ErrorIs(t, err, io.EOF) + require.Equal(t, txData{}, returnedTxData) + + // Set the pending channel + // The nextTxData function should still return EOF + // since the pending channel has no frames + m.ensurePendingChannel(eth.BlockID{}) + returnedTxData, err = m.nextTxData() + require.ErrorIs(t, err, io.EOF) + require.Equal(t, txData{}, returnedTxData) + + // Manually push a frame into the pending channel + channelID := m.pendingChannel.ID() + frame := frameData{ + data: []byte{}, + id: frameID{ + chID: channelID, + frameNumber: uint16(0), + }, + } + m.pendingChannel.PushFrame(frame) + require.Equal(t, 1, m.pendingChannel.NumFrames()) + + // Now the nextTxData function should return the frame + returnedTxData, err = m.nextTxData() + expectedTxData := txData{frame} + expectedChannelID := expectedTxData.ID() + require.NoError(t, err) + require.Equal(t, expectedTxData, returnedTxData) + require.Equal(t, 0, m.pendingChannel.NumFrames()) + require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) } func TestChannelManager_TxResend(t *testing.T) { require := require.New(t) rng := rand.New(rand.NewSource(time.Now().UnixNano())) log := testlog.Logger(t, log.LvlError) - m := batcher.NewChannelManager(log, batcher.ChannelConfig{ + m := NewChannelManager(log, ChannelConfig{ TargetFrameSize: 0, MaxFrameSize: 120_000, ApproxComprRatio: 1.0, From 5bae90583b3072d771a181d58c10422d7c56ab27 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Wed, 15 Mar 2023 11:40:45 -0400 Subject: [PATCH 022/404] add tx confirmation test :test_tube: --- op-batcher/batcher/channel_manager_test.go | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 4296f35ee..7699665e2 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -128,6 +128,59 @@ func TestChannelManagerNextTxData(t *testing.T) { require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) } +// TestChannelManagerTxConfirmed checks the [ChannelManager.TxConfirmed] function. +func TestChannelManagerTxConfirmed(t *testing.T) { + // Create a channel manager + log := testlog.Logger(t, log.LvlCrit) + m := NewChannelManager(log, ChannelConfig{ + // Need to set the channel timeout here so we don't clear pending + // channels on confirmation. This would result in [TxConfirmed] + // clearing confirmed transactions, and reseting the pendingChannels map + ChannelTimeout: 10, + }) + + // Let's add a valid pending transaction to the channel manager + // So we can demonstrate that TxConfirmed's correctness + m.ensurePendingChannel(eth.BlockID{}) + channelID := m.pendingChannel.ID() + frame := frameData{ + data: []byte{}, + id: frameID{ + chID: channelID, + frameNumber: uint16(0), + }, + } + m.pendingChannel.PushFrame(frame) + require.Equal(t, 1, m.pendingChannel.NumFrames()) + returnedTxData, err := m.nextTxData() + expectedTxData := txData{frame} + expectedChannelID := expectedTxData.ID() + require.NoError(t, err) + require.Equal(t, expectedTxData, returnedTxData) + require.Equal(t, 0, m.pendingChannel.NumFrames()) + require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) + require.Equal(t, 1, len(m.pendingTransactions)) + + // An unknown pending transaction should not be marked as confirmed + // and should not be removed from the pending transactions map + actualChannelID := m.pendingChannel.ID() + unknownChannelID := derive.ChannelID([derive.ChannelIDLength]byte{0x69}) + require.NotEqual(t, actualChannelID, unknownChannelID) + unknownTxID := frameID{chID: unknownChannelID, frameNumber: 0} + blockID := eth.BlockID{Number: 0, Hash: common.Hash{0x69}} + m.TxConfirmed(unknownTxID, blockID) + require.Empty(t, m.confirmedTransactions) + require.Equal(t, 1, len(m.pendingTransactions)) + + // Now let's mark the pending transaction as confirmed + // and check that it is removed from the pending transactions map + // and added to the confirmed transactions map + m.TxConfirmed(expectedChannelID, blockID) + require.Empty(t, m.pendingTransactions) + require.Equal(t, 1, len(m.confirmedTransactions)) + require.Equal(t, blockID, m.confirmedTransactions[expectedChannelID]) +} + func TestChannelManager_TxResend(t *testing.T) { require := require.New(t) rng := rand.New(rand.NewSource(time.Now().UnixNano())) From 272cb6c674146a10d56abe4dc8ac8f2a0b120b33 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Tue, 14 Mar 2023 13:47:08 -0500 Subject: [PATCH 023/404] feat(docs/op-stack): Slightly simpler installation --- docs/op-stack/src/docs/build/getting-started.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/op-stack/src/docs/build/getting-started.md b/docs/op-stack/src/docs/build/getting-started.md index 24dd8fff0..c4a4715f7 100644 --- a/docs/op-stack/src/docs/build/getting-started.md +++ b/docs/op-stack/src/docs/build/getting-started.md @@ -39,12 +39,11 @@ This tutorial was checked on: | Software | Version | Installation command(s) | | -------- | ---------- | - | | Ubuntu | 20.04 LTS | | -| git | OS default | | -| make | 4.2.1-1.2 | `sudo apt install -y make` +| git, curl, and make | OS default | `sudo apt install -y git curl make` | | Go | 1.20 | `sudo apt update`
`wget https://go.dev/dl/go1.20.linux-amd64.tar.gz`
`tar xvzf go1.20.linux-amd64.tar.gz`
`sudo cp go/bin/go /usr/bin/go`
`sudo mv go /usr/lib`
`echo export GOROOT=/usr/lib/go >> ~/.bashrc` -| Node | 16.19.0 | `curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -`
`sudo apt-get install -y nodejs` +| Node | 16.19.0 | `curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -`
`sudo apt-get install -y nodejs npm` | yarn | 1.22.19 | `sudo npm install -g yarn` -| Foundry | 0.2.0 | `curl -L https://foundry.paradigm.xyz | bash`
`sudo bash`
`foundryup` +| Foundry | 0.2.0 | `curl -L https://foundry.paradigm.xyz | bash`
`. ~/.bashrc`
`foundryup` ## Build the Source Code @@ -74,7 +73,8 @@ We’re going to be spinning up an EVM Rollup from the OP Stack source code. Yo 1. Build the various packages inside of the Optimism Monorepo. ```bash - make build + make op-node op-batcher + yarn build ``` ### Build op-geth From 814e68f796ce10a92ac7ea57c875fc5bed531864 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Wed, 15 Mar 2023 13:44:30 -0400 Subject: [PATCH 024/404] test channel manager clearing wipes state :test_tube: --- op-batcher/batcher/channel_manager_test.go | 115 +++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 7699665e2..2bd612d3c 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -128,6 +128,79 @@ func TestChannelManagerNextTxData(t *testing.T) { require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) } +// TestClearChannelManager tests clearing the channel manager. +func TestClearChannelManager(t *testing.T) { + // Create a channel manager + log := testlog.Logger(t, log.LvlCrit) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + m := NewChannelManager(log, ChannelConfig{ + // Need to set the channel timeout here so we don't clear pending + // channels on confirmation. This would result in [TxConfirmed] + // clearing confirmed transactions, and reseting the pendingChannels map + ChannelTimeout: 10, + // Have to set the max frame size here otherwise the channel builder would not + // be able to output any frames + MaxFrameSize: 1, + }) + + // Channel Manager state should be empty by default + require.Empty(t, m.blocks) + require.Equal(t, common.Hash{}, m.tip) + require.Nil(t, m.pendingChannel) + require.Empty(t, m.pendingTransactions) + require.Empty(t, m.confirmedTransactions) + + // Add a block to the channel manager + a, _ := derivetest.RandomL2Block(rng, 4) + newL1Tip := a.Hash() + l1BlockID := eth.BlockID{ + Hash: a.Hash(), + Number: a.NumberU64(), + } + err := m.AddL2Block(a) + require.NoError(t, err) + + // Make sure there is a channel builder + m.ensurePendingChannel(l1BlockID) + require.NotNil(t, m.pendingChannel) + require.Equal(t, 0, len(m.confirmedTransactions)) + + // Process the blocks + // We should have a pending channel with 1 frame + // and no more blocks since processBlocks consumes + // the list + err = m.processBlocks() + require.NoError(t, err) + err = m.pendingChannel.OutputFrames() + require.NoError(t, err) + _, err = m.nextTxData() + require.NoError(t, err) + require.Equal(t, 0, len(m.blocks)) + require.Equal(t, newL1Tip, m.tip) + require.Equal(t, 1, len(m.pendingTransactions)) + + // Add a new block so we can test clearing + // the channel manager with a full state + b := types.NewBlock(&types.Header{ + Number: big.NewInt(1), + ParentHash: a.Hash(), + }, nil, nil, nil, nil) + err = m.AddL2Block(b) + require.NoError(t, err) + require.Equal(t, 1, len(m.blocks)) + require.Equal(t, b.Hash(), m.tip) + + // Clear the channel manager + m.Clear() + + // Check that the entire channel manager state cleared + require.Empty(t, m.blocks) + require.Equal(t, common.Hash{}, m.tip) + require.Nil(t, m.pendingChannel) + require.Empty(t, m.pendingTransactions) + require.Empty(t, m.confirmedTransactions) +} + // TestChannelManagerTxConfirmed checks the [ChannelManager.TxConfirmed] function. func TestChannelManagerTxConfirmed(t *testing.T) { // Create a channel manager @@ -181,6 +254,48 @@ func TestChannelManagerTxConfirmed(t *testing.T) { require.Equal(t, blockID, m.confirmedTransactions[expectedChannelID]) } +// TestChannelManagerTxFailed checks the [ChannelManager.TxFailed] function. +func TestChannelManagerTxFailed(t *testing.T) { + // Create a channel manager + log := testlog.Logger(t, log.LvlCrit) + m := NewChannelManager(log, ChannelConfig{}) + + // Let's add a valid pending transaction to the channel + // manager so we can demonstrate correctness + m.ensurePendingChannel(eth.BlockID{}) + channelID := m.pendingChannel.ID() + frame := frameData{ + data: []byte{}, + id: frameID{ + chID: channelID, + frameNumber: uint16(0), + }, + } + m.pendingChannel.PushFrame(frame) + require.Equal(t, 1, m.pendingChannel.NumFrames()) + returnedTxData, err := m.nextTxData() + expectedTxData := txData{frame} + expectedChannelID := expectedTxData.ID() + require.NoError(t, err) + require.Equal(t, expectedTxData, returnedTxData) + require.Equal(t, 0, m.pendingChannel.NumFrames()) + require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) + require.Equal(t, 1, len(m.pendingTransactions)) + + // Trying to mark an unknown pending transaction as failed + // shouldn't modify state + m.TxFailed(frameID{}) + require.Equal(t, 0, m.pendingChannel.NumFrames()) + require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) + + // Now we still have a pending transaction + // Let's mark it as failed + m.TxFailed(expectedChannelID) + require.Empty(t, m.pendingTransactions) + // There should be a frame in the pending channel now + require.Equal(t, 1, m.pendingChannel.NumFrames()) +} + func TestChannelManager_TxResend(t *testing.T) { require := require.New(t) rng := rand.New(rand.NewSource(time.Now().UnixNano())) From 7bbe25dd4e1911b5df9eaff311fd3346e7fda30d Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Wed, 15 Mar 2023 13:56:38 -0400 Subject: [PATCH 025/404] fix golangci nits :gear: --- op-batcher/batcher/channel_manager_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 2bd612d3c..afa7c2a18 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -101,7 +101,8 @@ func TestChannelManagerNextTxData(t *testing.T) { // Set the pending channel // The nextTxData function should still return EOF // since the pending channel has no frames - m.ensurePendingChannel(eth.BlockID{}) + err = m.ensurePendingChannel(eth.BlockID{}) + require.NoError(t, err) returnedTxData, err = m.nextTxData() require.ErrorIs(t, err, io.EOF) require.Equal(t, txData{}, returnedTxData) @@ -161,7 +162,8 @@ func TestClearChannelManager(t *testing.T) { require.NoError(t, err) // Make sure there is a channel builder - m.ensurePendingChannel(l1BlockID) + err = m.ensurePendingChannel(l1BlockID) + require.NoError(t, err) require.NotNil(t, m.pendingChannel) require.Equal(t, 0, len(m.confirmedTransactions)) @@ -214,7 +216,8 @@ func TestChannelManagerTxConfirmed(t *testing.T) { // Let's add a valid pending transaction to the channel manager // So we can demonstrate that TxConfirmed's correctness - m.ensurePendingChannel(eth.BlockID{}) + err := m.ensurePendingChannel(eth.BlockID{}) + require.NoError(t, err) channelID := m.pendingChannel.ID() frame := frameData{ data: []byte{}, @@ -262,7 +265,8 @@ func TestChannelManagerTxFailed(t *testing.T) { // Let's add a valid pending transaction to the channel // manager so we can demonstrate correctness - m.ensurePendingChannel(eth.BlockID{}) + err := m.ensurePendingChannel(eth.BlockID{}) + require.NoError(t, err) channelID := m.pendingChannel.ID() frame := frameData{ data: []byte{}, From dff0a1653a908711c457d4436010ae38881f4a3f Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Wed, 15 Mar 2023 13:30:07 -0500 Subject: [PATCH 026/404] feat(docs/op-stack): Update explorer with example of indexing --- docs/op-stack/src/docs/build/explorer.md | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/op-stack/src/docs/build/explorer.md b/docs/op-stack/src/docs/build/explorer.md index 943aec92f..02a54be5f 100644 --- a/docs/op-stack/src/docs/build/explorer.md +++ b/docs/op-stack/src/docs/build/explorer.md @@ -59,3 +59,66 @@ Download and install [Docker engine](https://docs.docker.com/engine/install/#ser After the docker containers start, browse to http:// < *computer running Blockscout* > :4000 to view the user interface. You can also use the [API](https://docs.blockscout.com/for-users/api) + +### GraphQL + +Blockscout's API includes [GraphQL](https://graphql.org/) support under `/graphiql`. +For example, this query looks at addresses. + +``` +query { + addresses(hashes:[ + "0xcB69A90Aa5311e0e9141a66212489bAfb48b9340", + "0xC2dfA7205088179A8644b9fDCecD6d9bED854Cfe"]) +``` + +GraphQL queries start with a top level entity (or entities). +In this case, our [top level query](https://docs.blockscout.com/for-users/api/graphql#queries) is for multiple addresses. + +Note that you can only query on fields that are indexed. +For example, here we query on the addresses. +However, we couldn't query on `contractCode` or `fetchedCoinBalance`. + +``` + { + hash + contractCode + fetchedCoinBalance +``` + +The fields above are fetched from the address table. + +``` + transactions(first:5) { +``` + +We can also fetch the transactions that include the address (either as source or destination). +The API does not let us fetch an unlimited number of transactions, so here we ask for the first 5. + + +``` + edges { + node { +``` + +Because this is a [graph](https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)), the entities that connect two types, for example addresses and transactions, are called `edges`. +At the other end of each edge there is a transaction, which is a separate `node`. + +``` + hash + fromAddressHash + toAddressHash + input + } +``` + +These are the fields we read for each transaction. + +``` + } + } + } +} +``` + +Finally, close all the brackets. \ No newline at end of file From dbb46473a97dba6335dee7c53fe3d81e7ef421b0 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 15 Mar 2023 13:26:52 -0400 Subject: [PATCH 027/404] fix(docs): improve sidebar styling Fixes some bugs in sidebar styling for the OP Stack docs. --- docs/op-stack/src/.vuepress/styles/index.styl | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/op-stack/src/.vuepress/styles/index.styl b/docs/op-stack/src/.vuepress/styles/index.styl index b44913231..d24d368f3 100644 --- a/docs/op-stack/src/.vuepress/styles/index.styl +++ b/docs/op-stack/src/.vuepress/styles/index.styl @@ -19,13 +19,13 @@ aside.sidebar { p.sidebar-heading { color: #323A43 !important; font-family: 'Open Sans', sans-serif; - font-weight: 600; + font-weight: 600 !important; font-size: 14px !important; line-height: 24px !important; min-height: 36px; - margin-left: 32px; + margin-left: 20px; padding: 8px 16px !important; - width: calc(100% - 64px) !important; + width: calc(100% - 60px) !important; border-radius: 8px; } @@ -34,18 +34,20 @@ a.sidebar-link { font-size: 14px !important; line-height: 24px !important; min-height: 36px; - margin-left: 32px; + margin-top: 3px; + margin-left: 20px; padding: 8px 16px !important; - width: calc(100% - 64px) !important; + width: calc(100% - 60px) !important; border-radius: 8px; } -section.sidebar-group a.sidebar-link { - margin-left: 44px; - width: calc(100% - 64px) !important; +section.sidebar-group a.sidebar-link, +section.sidebar-group p.sidebar-heading.clickable { + margin-left: 32px; + width: calc(100% - 60px) !important; } -.sidebar-links:not(.sidebar-group-items) > li > a.sidebar-link { +.sidebar-links:not(.sidebar-group-items) > li > a.sidebar-link { font-weight: 600 !important; color: #323A43 !important; } From e4a48f8c6b90c56e3847fa75486b5040bd3f3378 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Wed, 15 Mar 2023 16:05:27 -0400 Subject: [PATCH 028/404] pending channel timeout tests :test_tube: --- op-batcher/batcher/channel_manager_test.go | 86 ++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index afa7c2a18..da0922419 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -18,6 +18,92 @@ import ( "github.com/stretchr/testify/require" ) +// TestChannelManagerAddL2Block tests adding an L2 block to the channel manager. +func TestChannelManagerAddL2Block(t *testing.T) { + log := testlog.Logger(t, log.LvlCrit) + m := NewChannelManager(log, ChannelConfig{}) + + // Add one block and assert state changes + a := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, nil, nil, nil, nil) + err := m.AddL2Block(a) + require.NoError(t, err) + require.Equal(t, []*types.Block{a}, m.blocks) + require.Equal(t, a.Hash(), m.tip) + + // Add another block and assert state changes + b := types.NewBlock(&types.Header{ + Number: big.NewInt(1), + ParentHash: a.Hash(), + }, nil, nil, nil, nil) + err = m.AddL2Block(b) + require.NoError(t, err) + require.Equal(t, []*types.Block{a, b}, m.blocks) + require.Equal(t, b.Hash(), m.tip) + + // Adding a block with an invalid parent hash should fail + c := types.NewBlock(&types.Header{ + Number: big.NewInt(2), + ParentHash: common.Hash{}, + }, nil, nil, nil, nil) + err = m.AddL2Block(c) + require.ErrorIs(t, ErrReorg, err) +} + +// TestPendingChannelTimeout tests that the channel manager +// correctly identifies when a pending channel is timed out. +func TestPendingChannelTimeout(t *testing.T) { + // Create a new channel manager with a ChannelTimeout + log := testlog.Logger(t, log.LvlCrit) + m := NewChannelManager(log, ChannelConfig{ + ChannelTimeout: 100, + }) + + // Pending channel is nil so is cannot be timed out + timeout := m.pendingChannelIsTimedOut() + require.False(t, timeout) + + // Set the pending channel + err := m.ensurePendingChannel(eth.BlockID{}) + require.NoError(t, err) + + // There are no confirmed transactions so + // the pending channel cannot be timed out + timeout = m.pendingChannelIsTimedOut() + require.False(t, timeout) + + // Manually set a confirmed transactions + // To avoid other methods clearing state + m.confirmedTransactions[frameID{ + frameNumber: 0, + }] = eth.BlockID{ + Number: 0, + } + m.confirmedTransactions[frameID{ + frameNumber: 1, + }] = eth.BlockID{ + Number: 99, + } + + // Since the ChannelTimeout is 100, the + // pending channel should not be timed out + timeout = m.pendingChannelIsTimedOut() + require.False(t, timeout) + + // Add a confirmed transaction with a higher number + // than the ChannelTimeout + m.confirmedTransactions[frameID{ + frameNumber: 2, + }] = eth.BlockID{ + Number: 101, + } + + // Now the pending channel should be timed out + timeout = m.pendingChannelIsTimedOut() + require.True(t, timeout) +} + // TestChannelManagerReturnsErrReorg ensures that the channel manager // detects a reorg when it has cached L1 blocks. func TestChannelManagerReturnsErrReorg(t *testing.T) { From 664d979906464369b087f5ae5acb538014918134 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Wed, 15 Mar 2023 21:27:16 -0500 Subject: [PATCH 029/404] feat(docs/op-stack): @tremarkley 's autorefresh --- docs/op-stack/src/.vuepress/enhanceApp.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/op-stack/src/.vuepress/enhanceApp.js b/docs/op-stack/src/.vuepress/enhanceApp.js index 6e6764caf..89bc2f0e6 100644 --- a/docs/op-stack/src/.vuepress/enhanceApp.js +++ b/docs/op-stack/src/.vuepress/enhanceApp.js @@ -1,5 +1,19 @@ +import event from '@vuepress/plugin-pwa/lib/event' + export default ({ router }) => { + registerAutoReload(); + router.addRoutes([ { path: '/docs/', redirect: '/' }, ]) } + +// When new content is detected by the app, this will automatically +// refresh the page, so that users do not need to manually click +// the refresh button. For more details see: +// https://linear.app/optimism/issue/FE-1003/investigate-archive-issue-on-docs +const registerAutoReload = () => { + event.$on('sw-updated', e => e.skipWaiting().then(() => { + location.reload(true); + })) +} From a13119772e465e51ade60713c3dc2a15a178a669 Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Tue, 14 Mar 2023 09:41:36 -0700 Subject: [PATCH 030/404] op-proposer: Increase sendTx timeout This increase the send transaction timeout while decreasing the transaction creation timeout. Using idfferent timeouts is better to catch problems because we expect different steps to take different amounts of time. --- op-proposer/proposer/l2_output_submitter.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index cde9c485b..c4e946e96 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -384,30 +384,31 @@ func (l *L2OutputSubmitter) loop() { for { select { case <-ticker.C: - cCtx, cancel := context.WithTimeout(ctx, 3*time.Minute) + cCtx, cancel := context.WithTimeout(ctx, 30*time.Second) output, shouldPropose, err := l.FetchNextOutputInfo(cCtx) + cancel() if err != nil { - l.log.Error("Failed to fetch next output", "err", err) - cancel() break } if !shouldPropose { - cancel() break } + cCtx, cancel = context.WithTimeout(ctx, 30*time.Second) tx, err := l.CreateProposalTx(cCtx, output) + cancel() if err != nil { l.log.Error("Failed to create proposal transaction", "err", err) - cancel() break } + cCtx, cancel = context.WithTimeout(ctx, 10*time.Minute) if err := l.SendTransaction(cCtx, tx); err != nil { l.log.Error("Failed to send proposal transaction", "err", err) cancel() break + } else { + cancel() } - cancel() case <-l.done: return From 2339364ab1b3f2ab9729f15f975326a97526bdb6 Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Tue, 14 Mar 2023 09:43:20 -0700 Subject: [PATCH 031/404] op-batcher: Increase send transaction timeout This increases it from 100s (~3 mins) to 10 minutes. This is because sending transactions can take a long time. --- op-batcher/batcher/txmgr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-batcher/batcher/txmgr.go b/op-batcher/batcher/txmgr.go index 3cc6e8981..007daa437 100644 --- a/op-batcher/batcher/txmgr.go +++ b/op-batcher/batcher/txmgr.go @@ -55,7 +55,7 @@ func (t *TransactionManager) SendTransaction(ctx context.Context, data []byte) ( return nil, fmt.Errorf("failed to create tx: %w", err) } - ctx, cancel := context.WithTimeout(ctx, 100*time.Second) // TODO: Select a timeout that makes sense here. + ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) // TODO: Select a timeout that makes sense here. defer cancel() if receipt, err := t.txMgr.Send(ctx, tx); err != nil { t.log.Warn("unable to publish tx", "err", err, "data_size", len(data)) From 9a644dcf3f343e7ea2aa9f351df214862dfc77e8 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Thu, 16 Mar 2023 17:37:49 +0100 Subject: [PATCH 032/404] Fix backwards log in op-node (#5171) --- op-node/rollup/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 6f51ccaa2..59317ec66 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -157,7 +157,7 @@ func (cfg *Config) CheckL2ChainID(ctx context.Context, client L2Client) error { return err } if cfg.L2ChainID.Cmp(id) != 0 { - return fmt.Errorf("incorrect L2 RPC chain id %d, expected %d", cfg.L2ChainID, id) + return fmt.Errorf("incorrect L2 RPC chain id, expected from config %d, obtained from client %d", cfg.L2ChainID, id) } return nil } From 08b96e2f01c7850207ea1c1d9ed3297e096a688e Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Wed, 15 Mar 2023 12:35:20 -0700 Subject: [PATCH 033/404] op-node: More logging --- op-node/rollup/derive/batch_queue.go | 5 +++-- op-node/rollup/derive/channel_bank.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/op-node/rollup/derive/batch_queue.go b/op-node/rollup/derive/batch_queue.go index 789149443..c5994b86a 100644 --- a/op-node/rollup/derive/batch_queue.go +++ b/op-node/rollup/derive/batch_queue.go @@ -139,6 +139,7 @@ func (bq *BatchQueue) AddBatch(batch *BatchData, l2SafeHead eth.L2BlockRef) { if validity == BatchDrop { return // if we do drop the batch, CheckBatch will log the drop reason with WARN level. } + bq.log.Debug("Adding batch", "batch_timestamp", batch.Timestamp, "parent_hash", batch.ParentHash, "batch_epoch", batch.Epoch(), "txs", len(batch.Transactions)) bq.batches[batch.Timestamp] = append(bq.batches[batch.Timestamp], &data) } @@ -212,7 +213,7 @@ batchLoop: if nextBatch.Batch.EpochNum == rollup.Epoch(epoch.Number)+1 { bq.l1Blocks = bq.l1Blocks[1:] } - bq.log.Trace("Returning found batch", "epoch", epoch, "batch_epoch", nextBatch.Batch.EpochNum, "batch_timestamp", nextBatch.Batch.Timestamp) + bq.log.Info("Found next batch", "epoch", epoch, "batch_epoch", nextBatch.Batch.EpochNum, "batch_timestamp", nextBatch.Batch.Timestamp) return nextBatch.Batch, nil } @@ -241,7 +242,7 @@ batchLoop: // to preserve that L2 time >= L1 time. If this is the first block of the epoch, always generate a // batch to ensure that we at least have one batch per epoch. if nextTimestamp < nextEpoch.Time || firstOfEpoch { - bq.log.Trace("Generating next batch", "epoch", epoch, "timestamp", nextTimestamp) + bq.log.Info("Generating next batch", "epoch", epoch, "timestamp", nextTimestamp) return &BatchData{ BatchV1{ ParentHash: l2SafeHead.Hash, diff --git a/op-node/rollup/derive/channel_bank.go b/op-node/rollup/derive/channel_bank.go index dfa21580e..4243cc627 100644 --- a/op-node/rollup/derive/channel_bank.go +++ b/op-node/rollup/derive/channel_bank.go @@ -69,6 +69,7 @@ func (cb *ChannelBank) prune() { ch := cb.channels[id] cb.channelQueue = cb.channelQueue[1:] delete(cb.channels, id) + cb.log.Info("pruning channel", "channel", id, "totalSize", totalSize, "channel_size", ch.size, "remaining_channel_count", len(cb.channels)) totalSize -= ch.size } } @@ -86,6 +87,7 @@ func (cb *ChannelBank) IngestFrame(f Frame) { currentCh = NewChannel(f.ID, origin) cb.channels[f.ID] = currentCh cb.channelQueue = append(cb.channelQueue, f.ID) + log.Info("created new channel") } // check if the channel is not timed out @@ -114,7 +116,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) { ch := cb.channels[first] timedOut := ch.OpenBlockNumber()+cb.cfg.ChannelTimeout < cb.Origin().Number if timedOut { - cb.log.Debug("channel timed out", "channel", first, "frames", len(ch.inputs)) + cb.log.Info("channel timed out", "channel", first, "frames", len(ch.inputs)) delete(cb.channels, first) cb.channelQueue = cb.channelQueue[1:] return nil, nil // multiple different channels may all be timed out @@ -137,7 +139,6 @@ func (cb *ChannelBank) Read() (data []byte, err error) { // consistency around channel bank pruning which depends upon the order // of operations. func (cb *ChannelBank) NextData(ctx context.Context) ([]byte, error) { - // Do the read from the channel bank first data, err := cb.Read() if err == io.EOF { From b7d377f2b9107af8cde4ce2fe2410c1931fb39d4 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Thu, 16 Mar 2023 15:18:43 -0400 Subject: [PATCH 034/404] fix nits :poop: --- op-batcher/batcher/channel_manager_test.go | 47 ++-------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index da0922419..21d501d60 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -18,39 +18,6 @@ import ( "github.com/stretchr/testify/require" ) -// TestChannelManagerAddL2Block tests adding an L2 block to the channel manager. -func TestChannelManagerAddL2Block(t *testing.T) { - log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{}) - - // Add one block and assert state changes - a := types.NewBlock(&types.Header{ - Number: big.NewInt(0), - }, nil, nil, nil, nil) - err := m.AddL2Block(a) - require.NoError(t, err) - require.Equal(t, []*types.Block{a}, m.blocks) - require.Equal(t, a.Hash(), m.tip) - - // Add another block and assert state changes - b := types.NewBlock(&types.Header{ - Number: big.NewInt(1), - ParentHash: a.Hash(), - }, nil, nil, nil, nil) - err = m.AddL2Block(b) - require.NoError(t, err) - require.Equal(t, []*types.Block{a, b}, m.blocks) - require.Equal(t, b.Hash(), m.tip) - - // Adding a block with an invalid parent hash should fail - c := types.NewBlock(&types.Header{ - Number: big.NewInt(2), - ParentHash: common.Hash{}, - }, nil, nil, nil, nil) - err = m.AddL2Block(c) - require.ErrorIs(t, ErrReorg, err) -} - // TestPendingChannelTimeout tests that the channel manager // correctly identifies when a pending channel is timed out. func TestPendingChannelTimeout(t *testing.T) { @@ -75,16 +42,8 @@ func TestPendingChannelTimeout(t *testing.T) { // Manually set a confirmed transactions // To avoid other methods clearing state - m.confirmedTransactions[frameID{ - frameNumber: 0, - }] = eth.BlockID{ - Number: 0, - } - m.confirmedTransactions[frameID{ - frameNumber: 1, - }] = eth.BlockID{ - Number: 99, - } + m.confirmedTransactions[frameID{frameNumber: 0}] = eth.BlockID{Number: 0} + m.confirmedTransactions[frameID{frameNumber: 1}] = eth.BlockID{Number: 99} // Since the ChannelTimeout is 100, the // pending channel should not be timed out @@ -134,6 +93,8 @@ func TestChannelManagerReturnsErrReorg(t *testing.T) { require.NoError(t, err) err = m.AddL2Block(x) require.ErrorIs(t, err, ErrReorg) + + require.Equal(t, []*types.Block{a, b, c}, m.blocks) } // TestChannelManagerReturnsErrReorgWhenDrained ensures that the channel manager From d62fe5dedd571ad7d5a0743dd146ea3ae1942f3e Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Thu, 16 Mar 2023 12:25:01 -0700 Subject: [PATCH 035/404] op-node: Remove leftover log flags --- op-node/flags/flags.go | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 86d38ad62..44f23ac3c 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/sources" + oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/urfave/cli" ) @@ -113,23 +114,6 @@ var ( Required: false, Value: time.Second * 12 * 32, } - LogLevelFlag = cli.StringFlag{ - Name: "log.level", - Usage: "The lowest log level that will be output", - Value: "info", - EnvVar: prefixEnvVar("LOG_LEVEL"), - } - LogFormatFlag = cli.StringFlag{ - Name: "log.format", - Usage: "Format the log output. Supported formats: 'text', 'json'", - Value: "text", - EnvVar: prefixEnvVar("LOG_FORMAT"), - } - LogColorFlag = cli.BoolFlag{ - Name: "log.color", - Usage: "Color the log output", - EnvVar: prefixEnvVar("LOG_COLOR"), - } MetricsEnabledFlag = cli.BoolFlag{ Name: "metrics.enabled", Usage: "Enable the metrics server", @@ -200,7 +184,7 @@ var requiredFlags = []cli.Flag{ RPCListenPort, } -var optionalFlags = append([]cli.Flag{ +var optionalFlags = []cli.Flag{ RollupConfig, Network, L1TrustRPC, @@ -211,9 +195,6 @@ var optionalFlags = append([]cli.Flag{ SequencerStoppedFlag, SequencerL1Confs, L1EpochPollIntervalFlag, - LogLevelFlag, - LogFormatFlag, - LogColorFlag, RPCEnableAdmin, MetricsEnabledFlag, MetricsAddrFlag, @@ -226,10 +207,16 @@ var optionalFlags = append([]cli.Flag{ HeartbeatMonikerFlag, HeartbeatURLFlag, BackupL2UnsafeSyncRPC, -}, p2pFlags...) +} // Flags contains the list of configuration options available to the binary. -var Flags = append(requiredFlags, optionalFlags...) +var Flags []cli.Flag + +func init() { + optionalFlags = append(optionalFlags, p2pFlags...) + optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) + Flags = append(requiredFlags, optionalFlags...) +} func CheckRequired(ctx *cli.Context) error { l1NodeAddr := ctx.GlobalString(L1NodeAddr.Name) From 2e8bc95a23a1e1235c208070b7ba660d91f66ec8 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Thu, 16 Mar 2023 15:27:23 -0400 Subject: [PATCH 036/404] add back err string --- op-batcher/batcher/channel_builder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index bcadd2f5f..beee0d394 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -291,7 +291,7 @@ func FuzzSeqWindowZeroTimeoutClose(f *testing.F) { calculatedTimeout := epochNum + seqWindowSize - subSafetyMargin cb.checkTimeout(calculatedTimeout) if cb.timeout != 0 { - require.ErrorIs(t, cb.FullErr(), ErrSeqWindowClose) + require.ErrorIs(t, cb.FullErr(), ErrSeqWindowClose, "Sequence window close should be reached") } }) } From 4f05137d5d12923dd6f704d85ec1615f05dbb374 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Thu, 16 Mar 2023 15:30:26 -0400 Subject: [PATCH 037/404] remove unintentional comment --- op-batcher/batcher/channel_builder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index beee0d394..c63acdd31 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -437,7 +437,7 @@ func TestOutputFramesMaxFrameIndex(t *testing.T) { channelConfig := defaultTestChannelConfig channelConfig.MaxFrameSize = 1 channelConfig.TargetNumFrames = math.MaxInt - channelConfig.TargetFrameSize = 1 // math.MaxUint64 + channelConfig.TargetFrameSize = 1 channelConfig.ApproxComprRatio = 0 // Continuously add blocks until the max frame index is reached From cec949f9785c1def318114a1a3e0dea313324b86 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Thu, 16 Mar 2023 15:38:33 -0500 Subject: [PATCH 038/404] fix(docs/op-stack): Fix the superchain-diag diagram --- .../docs/understand/superchain-diag.png | Bin 68170 -> 43889 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/op-stack/src/assets/docs/understand/superchain-diag.png b/docs/op-stack/src/assets/docs/understand/superchain-diag.png index 02e639d5a99abd124e106f80099c97128c7c0f86..7c70243bd72c99df77f2cf3202eb100726ee6906 100644 GIT binary patch literal 43889 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 z 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 Xw%TpMjh!zy0e{vO_Ghb4d&mC|$u6=? From 4d2ddf8613f2c41df431d4b08248d4569cb5728c Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Thu, 16 Mar 2023 15:45:24 -0500 Subject: [PATCH 039/404] fix(docs/op-stack): In superchain-diag, 2->3 --- .../docs/understand/superchain-diag.png | Bin 43889 -> 65703 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/op-stack/src/assets/docs/understand/superchain-diag.png b/docs/op-stack/src/assets/docs/understand/superchain-diag.png index 7c70243bd72c99df77f2cf3202eb100726ee6906..79ea11f1e34e0dd2137d2b706fb885cb04c11ffa 100644 GIT binary patch literal 65703 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 literal 43889 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 z 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 Date: Tue, 14 Mar 2023 22:42:38 +0100 Subject: [PATCH 040/404] op-node: handle sequencer during catch-up, handle when engine state invalidates attributes --- op-node/rollup/derive/engine_queue.go | 21 ++- op-node/rollup/derive/engine_queue_test.go | 194 ++++++++++++++++++++- op-node/rollup/derive/pipeline.go | 6 + op-node/rollup/driver/driver.go | 1 + op-node/rollup/driver/sequencer.go | 21 ++- op-node/rollup/driver/state.go | 4 +- 6 files changed, 240 insertions(+), 7 deletions(-) diff --git a/op-node/rollup/derive/engine_queue.go b/op-node/rollup/derive/engine_queue.go index b3207063f..0feef1f2b 100644 --- a/op-node/rollup/derive/engine_queue.go +++ b/op-node/rollup/derive/engine_queue.go @@ -104,8 +104,10 @@ type EngineQueue struct { finalizedL1 eth.L1BlockRef - safeAttributes *eth.PayloadAttributes - unsafePayloads PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps + // The queued-up attributes + safeAttributesParent eth.L2BlockRef + safeAttributes *eth.PayloadAttributes + unsafePayloads PayloadsQueue // queue of unsafe payloads, ordered by ascending block number, may have gaps // Tracks which L2 blocks where last derived from which L1 block. At most finalityLookback large. finalityData []FinalityData @@ -225,6 +227,7 @@ func (eq *EngineQueue) Step(ctx context.Context) error { return err } else { eq.safeAttributes = next + eq.safeAttributesParent = eq.safeHead eq.log.Debug("Adding next safe attributes", "safe_head", eq.safeHead, "next", eq.safeAttributes) return NotEnoughData } @@ -427,6 +430,17 @@ func (eq *EngineQueue) tryNextUnsafePayload(ctx context.Context) error { } func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error { + // 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.ParentHash != eq.safeAttributesParent.Hash { + return NewResetError(fmt.Errorf("safe head changed to %s with parent %s, conflicting with queued safe attributes on top of %s", + eq.safeHead, eq.safeHead.ParentID(), eq.safeAttributesParent)) + } + eq.log.Warn("queued safe attributes are stale, safe-head progressed", + "safe_head", eq.safeHead, "safe_head_parent", eq.safeHead.ParentID(), "attributes_parent", eq.safeAttributesParent) + eq.safeAttributes = nil + return nil + } if eq.safeHead.Number < eq.unsafeHead.Number { return eq.consolidateNextSafeAttributes(ctx) } else if eq.safeHead.Number == eq.unsafeHead.Number { @@ -486,14 +500,15 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error { _, errType, err = eq.ConfirmPayload(ctx) } if err != nil { - _ = eq.CancelPayload(ctx, true) switch errType { case BlockInsertTemporaryErr: // RPC errors are recoverable, we can retry the buffered payload attributes later. return NewTemporaryError(fmt.Errorf("temporarily cannot insert new safe block: %w", err)) case BlockInsertPrestateErr: + _ = eq.CancelPayload(ctx, true) return NewResetError(fmt.Errorf("need reset to resolve pre-state problem: %w", err)) case BlockInsertPayloadErr: + _ = eq.CancelPayload(ctx, true) eq.log.Warn("could not process payload derived from L1 data, dropping batch", "err", err) // Count the number of deposits to see if the tx list is deposit only. depositCount := 0 diff --git a/op-node/rollup/derive/engine_queue_test.go b/op-node/rollup/derive/engine_queue_test.go index 440bb498e..bdcff3422 100644 --- a/op-node/rollup/derive/engine_queue_test.go +++ b/op-node/rollup/derive/engine_queue_test.go @@ -2,10 +2,13 @@ package derive import ( "context" + "fmt" "io" + "math/big" "math/rand" "testing" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" @@ -19,6 +22,7 @@ import ( type fakeAttributesQueue struct { origin eth.L1BlockRef + attrs *eth.PayloadAttributes } func (f *fakeAttributesQueue) Origin() eth.L1BlockRef { @@ -26,7 +30,10 @@ func (f *fakeAttributesQueue) Origin() eth.L1BlockRef { } func (f *fakeAttributesQueue) NextAttributes(_ context.Context, _ eth.L2BlockRef) (*eth.PayloadAttributes, error) { - return nil, io.EOF + if f.attrs == nil { + return nil, io.EOF + } + return f.attrs, nil } var _ NextAttributesProvider = (*fakeAttributesQueue)(nil) @@ -837,3 +844,188 @@ func TestVerifyNewL1Origin(t *testing.T) { }) } } + +func TestBlockBuildingRace(t *testing.T) { + logger := testlog.Logger(t, log.LvlInfo) + eng := &testutils.MockEngine{} + + 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, + } + l1BlockTime := uint64(2) + refB := eth.L1BlockRef{ + Hash: testutils.RandomHash(rng), + Number: refA.Number + 1, + ParentHash: refA.Hash, + Time: refA.Time + l1BlockTime, + } + 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, + } + refB0 := eth.L2BlockRef{ + Hash: testutils.RandomHash(rng), + Number: refA1.Number + 1, + ParentHash: refA1.Hash, + Time: refA1.Time + cfg.BlockTime, + L1Origin: refB.ID(), + SequenceNumber: 0, + } + t.Logf("A: %s", refA.Hash) + t.Logf("A0: %s", refA0.Hash) + t.Logf("A1: %s", refA1.Hash) + t.Logf("B: %s", refB.Hash) + t.Logf("B0: %s", refB0.Hash) + + l1F := &testutils.MockL1Source{} + + eng.ExpectL2BlockRefByLabel(eth.Finalized, refA0, nil) + eng.ExpectL2BlockRefByLabel(eth.Safe, refA0, nil) + eng.ExpectL2BlockRefByLabel(eth.Unsafe, refA0, nil) + l1F.ExpectL1BlockRefByNumber(refA.Number, refA, nil) + l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) + l1F.ExpectL1BlockRefByHash(refA.Hash, refA, nil) + + eng.ExpectSystemConfigByL2Hash(refA0.Hash, cfg.Genesis.SystemConfig, nil) + + metrics := &testutils.TestDerivationMetrics{} + + gasLimit := eth.Uint64Quantity(20_000_000) + attrs := ð.PayloadAttributes{ + Timestamp: eth.Uint64Quantity(refA1.Time), + PrevRandao: eth.Bytes32{}, + SuggestedFeeRecipient: common.Address{}, + Transactions: nil, + NoTxPool: false, + GasLimit: &gasLimit, + } + + prev := &fakeAttributesQueue{origin: refA, attrs: attrs} + eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F) + require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) + + t.Log(refB0) + + id := eth.PayloadID{0xff} + + preFc := ð.ForkchoiceState{ + HeadBlockHash: refA0.Hash, + SafeBlockHash: refA0.Hash, + FinalizedBlockHash: refA0.Hash, + } + preFcRes := ð.ForkchoiceUpdatedResult{ + PayloadStatus: eth.PayloadStatusV1{ + Status: eth.ExecutionValid, + LatestValidHash: &refA0.Hash, + ValidationError: nil, + }, + PayloadID: &id, + } + + // Expect initial forkchoice update + eng.ExpectForkchoiceUpdate(preFc, nil, preFcRes, nil) + require.NoError(t, eq.Step(context.Background()), "clean forkchoice state after reset") + + // Expect initial building update, to process the attributes we queued up + eng.ExpectForkchoiceUpdate(preFc, attrs, preFcRes, nil) + // Don't let the payload be confirmed straight away + mockErr := fmt.Errorf("mock error") + eng.ExpectGetPayload(id, nil, mockErr) + // The job will be not be cancelled, the untyped error is a temporary error + + require.ErrorIs(t, eq.Step(context.Background()), NotEnoughData, "queue up attributes") + require.ErrorIs(t, eq.Step(context.Background()), mockErr, "expecting to fail to process attributes") + require.NotNil(t, eq.safeAttributes, "still have attributes") + + // Now allow the building to complete + a1InfoTx, err := L1InfoDepositBytes(refA1.SequenceNumber, &testutils.MockBlockInfo{ + InfoHash: refA.Hash, + InfoParentHash: refA.ParentHash, + InfoCoinbase: common.Address{}, + InfoRoot: common.Hash{}, + InfoNum: refA.Number, + InfoTime: refA.Time, + InfoMixDigest: [32]byte{}, + InfoBaseFee: big.NewInt(7), + InfoReceiptRoot: common.Hash{}, + InfoGasUsed: 0, + }, cfg.Genesis.SystemConfig, false) + + require.NoError(t, err) + payloadA1 := ð.ExecutionPayload{ + ParentHash: refA1.ParentHash, + FeeRecipient: attrs.SuggestedFeeRecipient, + StateRoot: eth.Bytes32{}, + ReceiptsRoot: eth.Bytes32{}, + LogsBloom: eth.Bytes256{}, + PrevRandao: eth.Bytes32{}, + BlockNumber: eth.Uint64Quantity(refA1.Number), + GasLimit: gasLimit, + GasUsed: 0, + Timestamp: eth.Uint64Quantity(refA1.Time), + ExtraData: nil, + BaseFeePerGas: *uint256.NewInt(7), + BlockHash: refA1.Hash, + Transactions: []eth.Data{ + a1InfoTx, + }, + } + eng.ExpectGetPayload(id, payloadA1, nil) + eng.ExpectNewPayload(payloadA1, ð.PayloadStatusV1{ + Status: eth.ExecutionValid, + LatestValidHash: &refA1.Hash, + ValidationError: nil, + }, nil) + postFc := ð.ForkchoiceState{ + HeadBlockHash: refA1.Hash, + SafeBlockHash: refA1.Hash, + FinalizedBlockHash: refA0.Hash, + } + postFcRes := ð.ForkchoiceUpdatedResult{ + PayloadStatus: eth.PayloadStatusV1{ + Status: eth.ExecutionValid, + LatestValidHash: &refA1.Hash, + ValidationError: nil, + }, + PayloadID: &id, + } + eng.ExpectForkchoiceUpdate(postFc, nil, postFcRes, nil) + + // Now complete the job, as external user of the engine + _, _, err = eq.ConfirmPayload(context.Background()) + require.NoError(t, err) + require.Equal(t, refA1, eq.SafeL2Head(), "safe head should have changed") + + require.NoError(t, eq.Step(context.Background())) + require.Nil(t, eq.safeAttributes, "attributes should now be invalidated") + + l1F.AssertExpectations(t) + eng.AssertExpectations(t) +} diff --git a/op-node/rollup/derive/pipeline.go b/op-node/rollup/derive/pipeline.go index 33c86b525..5d53a1721 100644 --- a/op-node/rollup/derive/pipeline.go +++ b/op-node/rollup/derive/pipeline.go @@ -107,6 +107,12 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch } } +// EngineReady returns true if the engine is ready to be used. +// When it's being reset its state is inconsistent, and should not be used externally. +func (dp *DerivationPipeline) EngineReady() bool { + return dp.resetting > 0 +} + func (dp *DerivationPipeline) Reset() { dp.resetting = 0 } diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index 508035013..d94eada3c 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -56,6 +56,7 @@ type DerivationPipeline interface { SafeL2Head() eth.L2BlockRef UnsafeL2Head() eth.L2BlockRef Origin() eth.L1BlockRef + EngineReady() bool } type L1StateIface interface { diff --git a/op-node/rollup/driver/sequencer.go b/op-node/rollup/driver/sequencer.go index 4da401d28..e868b69d5 100644 --- a/op-node/rollup/driver/sequencer.go +++ b/op-node/rollup/driver/sequencer.go @@ -29,6 +29,10 @@ type SequencerMetrics interface { RecordSequencerReset() } +// Sequencing produces unsafe blocks only, and should not interrupt the L2 block building of safe blocks, +// e.g. when catching up with an L1 chain with existing batch data. +const safeBuildInterruptBackoff = 5 * time.Second + // Sequencer implements the sequencing interface of the driver: it starts and completes block building jobs. type Sequencer struct { log log.Logger @@ -123,6 +127,13 @@ func (d *Sequencer) CancelBuildingBlock(ctx context.Context) { // PlanNextSequencerAction returns a desired delay till the RunNextSequencerAction call. func (d *Sequencer) PlanNextSequencerAction() time.Duration { + // If the engine is busy building safe blocks (and thus changing the head that we would sync on top of), + // then give it time to sync up. + if onto, _, safe := d.engine.BuildingPayload(); safe { + d.log.Warn("delaying sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time) + return safeBuildInterruptBackoff + } + head := d.engine.UnsafeL2Head() now := d.timeNow() @@ -173,7 +184,7 @@ func (d *Sequencer) BuildingOnto() eth.L2BlockRef { // Only critical errors are bubbled up, other errors are handled internally. // Internally starting or sealing of a block may fail with a derivation-like error: // - If it is a critical error, the error is bubbled up to the caller. -// - If it is a reset error, the ResettableEngineControl used to build blocks is requested to reset, and a backoff aplies. +// - If it is a reset error, the ResettableEngineControl used to build blocks is requested to reset, and a backoff applies. // No attempt is made at completing the block building. // - If it is a temporary error, a backoff is applied to reattempt building later. // - If it is any other error, a backoff is applied and building is cancelled. @@ -187,8 +198,14 @@ func (d *Sequencer) BuildingOnto() eth.L2BlockRef { // since it can consolidate previously sequenced blocks by comparing sequenced inputs with derived inputs. // If the derivation pipeline does force a conflicting block, then an ongoing sequencer task might still finish, // but the derivation can continue to reset until the chain is correct. +// If the engine is currently building safe blocks, then that building is not interrupted, and sequencing is delayed. func (d *Sequencer) RunNextSequencerAction(ctx context.Context) (*eth.ExecutionPayload, error) { - if _, buildingID, _ := d.engine.BuildingPayload(); buildingID != (eth.PayloadID{}) { + if onto, buildingID, safe := d.engine.BuildingPayload(); buildingID != (eth.PayloadID{}) { + if safe { + d.log.Warn("avoiding sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time) + d.nextAction = d.timeNow().Add(safeBuildInterruptBackoff) + return nil, nil + } payload, err := d.CompleteBuildingBlock(ctx) if err != nil { if errors.Is(err, derive.ErrCritical) { diff --git a/op-node/rollup/driver/state.go b/op-node/rollup/driver/state.go index f099cb4a7..1387c9136 100644 --- a/op-node/rollup/driver/state.go +++ b/op-node/rollup/driver/state.go @@ -209,7 +209,9 @@ func (s *Driver) eventLoop() { for { // If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action. // This may adjust at any time based on fork-choice changes or previous errors. - if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped && s.l1State.L1Head() != (eth.L1BlockRef{}) { + // And avoid sequencing if the derivation pipeline indicates the engine is not ready. + if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped && + s.l1State.L1Head() != (eth.L1BlockRef{}) && s.derivation.EngineReady() { // update sequencer time if the head changed if s.sequencer.BuildingOnto().ID() != s.derivation.UnsafeL2Head().ID() { planSequencerAction() From e7f3c5b8e9b141e301e678156e17684fdfacf05c Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 15 Mar 2023 23:22:57 +0100 Subject: [PATCH 041/404] op-node: add sanity check to tryNextSafeAttributes function --- op-node/rollup/derive/engine_queue.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/op-node/rollup/derive/engine_queue.go b/op-node/rollup/derive/engine_queue.go index 0feef1f2b..226c699d1 100644 --- a/op-node/rollup/derive/engine_queue.go +++ b/op-node/rollup/derive/engine_queue.go @@ -430,6 +430,9 @@ func (eq *EngineQueue) tryNextUnsafePayload(ctx context.Context) error { } func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error { + if eq.safeAttributes == nil { // sanity check the attributes are there + return nil + } // 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.ParentHash != eq.safeAttributesParent.Hash { From a11d81c671825411fc8c5ee7717c7ff24c365940 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 16 Mar 2023 15:08:27 +0100 Subject: [PATCH 042/404] op-node: lint fix --- op-node/rollup/derive/engine_queue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-node/rollup/derive/engine_queue.go b/op-node/rollup/derive/engine_queue.go index 226c699d1..91a26cc46 100644 --- a/op-node/rollup/derive/engine_queue.go +++ b/op-node/rollup/derive/engine_queue.go @@ -430,7 +430,7 @@ func (eq *EngineQueue) tryNextUnsafePayload(ctx context.Context) error { } func (eq *EngineQueue) tryNextSafeAttributes(ctx context.Context) error { - if eq.safeAttributes == nil { // sanity check the attributes are there + if eq.safeAttributes == nil { // sanity check the attributes are there return nil } // validate the safe attributes before processing them. The engine may have completed processing them through other means. From c1985181e1f3957a18ec02d5a8eb6241741c6386 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 16 Mar 2023 18:34:24 +0100 Subject: [PATCH 043/404] op-node: engine queue test cleanup --- op-node/rollup/derive/engine_queue_test.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/op-node/rollup/derive/engine_queue_test.go b/op-node/rollup/derive/engine_queue_test.go index bdcff3422..b4dbd7438 100644 --- a/op-node/rollup/derive/engine_queue_test.go +++ b/op-node/rollup/derive/engine_queue_test.go @@ -860,13 +860,6 @@ func TestBlockBuildingRace(t *testing.T) { L1Origin: refA.ID(), SequenceNumber: 0, } - l1BlockTime := uint64(2) - refB := eth.L1BlockRef{ - Hash: testutils.RandomHash(rng), - Number: refA.Number + 1, - ParentHash: refA.Hash, - Time: refA.Time + l1BlockTime, - } cfg := &rollup.Config{ Genesis: rollup.Genesis{ L1: refA.ID(), @@ -890,19 +883,6 @@ func TestBlockBuildingRace(t *testing.T) { L1Origin: refA.ID(), SequenceNumber: 1, } - refB0 := eth.L2BlockRef{ - Hash: testutils.RandomHash(rng), - Number: refA1.Number + 1, - ParentHash: refA1.Hash, - Time: refA1.Time + cfg.BlockTime, - L1Origin: refB.ID(), - SequenceNumber: 0, - } - t.Logf("A: %s", refA.Hash) - t.Logf("A0: %s", refA0.Hash) - t.Logf("A1: %s", refA1.Hash) - t.Logf("B: %s", refB.Hash) - t.Logf("B0: %s", refB0.Hash) l1F := &testutils.MockL1Source{} @@ -931,8 +911,6 @@ func TestBlockBuildingRace(t *testing.T) { eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) - t.Log(refB0) - id := eth.PayloadID{0xff} preFc := ð.ForkchoiceState{ From 6603ed0738e095ca31ad341fbc559d1d2d6bc8a1 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 16 Mar 2023 20:31:07 +0100 Subject: [PATCH 044/404] op-node: review feedback, reduce waiting time after finding safe-block is still being processed --- op-node/rollup/driver/sequencer.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/op-node/rollup/driver/sequencer.go b/op-node/rollup/driver/sequencer.go index e868b69d5..af6d469cc 100644 --- a/op-node/rollup/driver/sequencer.go +++ b/op-node/rollup/driver/sequencer.go @@ -29,10 +29,6 @@ type SequencerMetrics interface { RecordSequencerReset() } -// Sequencing produces unsafe blocks only, and should not interrupt the L2 block building of safe blocks, -// e.g. when catching up with an L1 chain with existing batch data. -const safeBuildInterruptBackoff = 5 * time.Second - // Sequencer implements the sequencing interface of the driver: it starts and completes block building jobs. type Sequencer struct { log log.Logger @@ -131,7 +127,8 @@ func (d *Sequencer) PlanNextSequencerAction() time.Duration { // then give it time to sync up. if onto, _, safe := d.engine.BuildingPayload(); safe { d.log.Warn("delaying sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time) - return safeBuildInterruptBackoff + // approximates the worst-case time it takes to build a block, to reattempt sequencing after. + return time.Second * time.Duration(d.config.BlockTime) } head := d.engine.UnsafeL2Head() @@ -203,7 +200,8 @@ func (d *Sequencer) RunNextSequencerAction(ctx context.Context) (*eth.ExecutionP if onto, buildingID, safe := d.engine.BuildingPayload(); buildingID != (eth.PayloadID{}) { if safe { d.log.Warn("avoiding sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time) - d.nextAction = d.timeNow().Add(safeBuildInterruptBackoff) + // approximates the worst-case time it takes to build a block, to reattempt sequencing after. + d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.config.BlockTime)) return nil, nil } payload, err := d.CompleteBuildingBlock(ctx) From c5f5c0ce5a369548268971b51f12139622751946 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 16 Mar 2023 22:44:51 +0100 Subject: [PATCH 045/404] differential-testing: fix lint issue in diff testing --- .../contracts-bedrock/scripts/differential-testing/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/differential-testing/utils.go b/packages/contracts-bedrock/scripts/differential-testing/utils.go index b789a0e79..f7e866fc5 100644 --- a/packages/contracts-bedrock/scripts/differential-testing/utils.go +++ b/packages/contracts-bedrock/scripts/differential-testing/utils.go @@ -27,7 +27,7 @@ func checkOk(ok bool) { // Shorthand to ease go's god awful error handling func checkErr(err error, failReason string) { if err != nil { - panic(fmt.Errorf("%s: %s", failReason, err)) + panic(fmt.Errorf("%s: %w", failReason, err)) } } From c7babfb67bcfd54f32fa4740cc436fb0128e2541 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 15 Mar 2023 18:06:31 +0100 Subject: [PATCH 046/404] op-e2e: test high-demand large L2 blocks case --- op-e2e/actions/l1_miner.go | 40 +++++---- op-e2e/actions/l2_batcher.go | 14 +++- op-e2e/actions/l2_batcher_test.go | 132 ++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 19 deletions(-) diff --git a/op-e2e/actions/l1_miner.go b/op-e2e/actions/l1_miner.go index 673eea5d9..8e3afe26b 100644 --- a/op-e2e/actions/l1_miner.go +++ b/op-e2e/actions/l1_miner.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/require" ) // L1Miner wraps a L1Replica with instrumented block building ability. @@ -100,26 +101,33 @@ func (s *L1Miner) ActL1IncludeTx(from common.Address) Action { t.Fatalf("no pending txs from %s, and have %d unprocessable queued txs from this account", from, len(q)) } tx := txs[i] - if tx.Gas() > s.l1BuildingHeader.GasLimit { - t.Fatalf("tx consumes %d gas, more than available in L1 block %d", tx.Gas(), s.l1BuildingHeader.GasLimit) - } - if tx.Gas() > uint64(*s.l1GasPool) { - t.InvalidAction("action takes too much gas: %d, only have %d", tx.Gas(), uint64(*s.l1GasPool)) - return - } + s.IncludeTx(t, tx) s.pendingIndices[from] = i + 1 // won't retry the tx - s.l1BuildingState.SetTxContext(tx.Hash(), len(s.l1Transactions)) - receipt, err := core.ApplyTransaction(s.l1Cfg.Config, s.l1Chain, &s.l1BuildingHeader.Coinbase, - s.l1GasPool, s.l1BuildingState, s.l1BuildingHeader, tx, &s.l1BuildingHeader.GasUsed, *s.l1Chain.GetVMConfig()) - if err != nil { - s.l1TxFailed = append(s.l1TxFailed, tx) - t.Fatalf("failed to apply transaction to L1 block (tx %d): %w", len(s.l1Transactions), err) - } - s.l1Receipts = append(s.l1Receipts, receipt) - s.l1Transactions = append(s.l1Transactions, tx) } } +func (s *L1Miner) IncludeTx(t Testing, tx *types.Transaction) { + from, err := s.l1Signer.Sender(tx) + require.NoError(t, err) + s.log.Info("including tx", "nonce", tx.Nonce(), "from", from) + if tx.Gas() > s.l1BuildingHeader.GasLimit { + t.Fatalf("tx consumes %d gas, more than available in L1 block %d", tx.Gas(), s.l1BuildingHeader.GasLimit) + } + if tx.Gas() > uint64(*s.l1GasPool) { + t.InvalidAction("action takes too much gas: %d, only have %d", tx.Gas(), uint64(*s.l1GasPool)) + return + } + s.l1BuildingState.SetTxContext(tx.Hash(), len(s.l1Transactions)) + receipt, err := core.ApplyTransaction(s.l1Cfg.Config, s.l1Chain, &s.l1BuildingHeader.Coinbase, + s.l1GasPool, s.l1BuildingState, s.l1BuildingHeader, tx, &s.l1BuildingHeader.GasUsed, *s.l1Chain.GetVMConfig()) + if err != nil { + s.l1TxFailed = append(s.l1TxFailed, tx) + t.Fatalf("failed to apply transaction to L1 block (tx %d): %v", len(s.l1Transactions), err) + } + s.l1Receipts = append(s.l1Receipts, receipt) + s.l1Transactions = append(s.l1Transactions, tx) +} + func (s *L1Miner) ActL1SetFeeRecipient(coinbase common.Address) { s.prefCoinbase = coinbase if s.l1Building { diff --git a/op-e2e/actions/l2_batcher.go b/op-e2e/actions/l2_batcher.go index b5e353d99..81909022d 100644 --- a/op-e2e/actions/l2_batcher.go +++ b/op-e2e/actions/l2_batcher.go @@ -91,6 +91,10 @@ func (s *L2Batcher) SubmittingData() bool { // ActL2BatchBuffer adds the next L2 block to the batch buffer. // If the buffer is being submitted, the buffer is wiped. func (s *L2Batcher) ActL2BatchBuffer(t Testing) { + require.NoError(t, s.Buffer(t), "failed to add block to channel") +} + +func (s *L2Batcher) Buffer(t Testing) error { if s.l2Submitting { // break ongoing submitting work if necessary s.l2ChannelOut = nil s.l2Submitting = false @@ -120,7 +124,7 @@ func (s *L2Batcher) ActL2BatchBuffer(t Testing) { s.l2ChannelOut = nil } else { s.log.Info("nothing left to submit") - return + return nil } } // Create channel if we don't have one yet @@ -143,9 +147,10 @@ func (s *L2Batcher) ActL2BatchBuffer(t Testing) { s.l2ChannelOut = nil } if _, err := s.l2ChannelOut.AddBlock(block); err != nil { // should always succeed - t.Fatalf("failed to add block to channel: %v", err) + return err } s.l2BufferedBlock = eth.ToBlockID(block) + return nil } func (s *L2Batcher) ActL2ChannelClose(t Testing) { @@ -158,7 +163,7 @@ func (s *L2Batcher) ActL2ChannelClose(t Testing) { } // ActL2BatchSubmit constructs a batch tx from previous buffered L2 blocks, and submits it to L1 -func (s *L2Batcher) ActL2BatchSubmit(t Testing) { +func (s *L2Batcher) ActL2BatchSubmit(t Testing, txOpts ...func(tx *types.DynamicFeeTx)) { // Don't run this action if there's no data to submit if s.l2ChannelOut == nil { t.InvalidAction("need to buffer data first, cannot batch submit with empty buffer") @@ -192,6 +197,9 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing) { GasFeeCap: gasFeeCap, Data: data.Bytes(), } + for _, opt := range txOpts { + opt(rawTx) + } gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas diff --git a/op-e2e/actions/l2_batcher_test.go b/op-e2e/actions/l2_batcher_test.go index 1f075107a..1038bb1e3 100644 --- a/op-e2e/actions/l2_batcher_test.go +++ b/op-e2e/actions/l2_batcher_test.go @@ -1,10 +1,13 @@ package actions import ( + "crypto/rand" + "errors" "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -12,6 +15,7 @@ import ( "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/testlog" ) @@ -378,3 +382,131 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { sequencer.ActL2PipelineFull(t) require.Equal(t, sequencer.L2Unsafe(), sequencer.L2Safe(), "same for sequencer") } + +// TestBigL2Txs tests a high-throughput case with constrained batcher: +// - Fill 100 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 +// (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 +// - Defer all data-tx inclusion till the end +// - Fill L1 blocks with data-txs until we have processed them all +// - Run the verifier, and check if it derives the same L2 chain as was created by the sequencer. +// +// The goal of this test is to quickly run through an otherwise very slow process of submitting and including lots of data. +// This does not test the batcher code, but is really focused at testing the batcher utils +// and channel-decoding verifier code in the derive package. +func TestBigL2Txs(gt *testing.T) { + t := NewDefaultTesting(gt) + p := &e2eutils.TestParams{ + MaxSequencerDrift: 100, + SequencerWindowSize: 1000, + ChannelTimeout: 200, // give enough space to buffer large amounts of data before submitting it + } + dp := e2eutils.MakeDeployParams(t, p) + sd := e2eutils.Setup(t, dp, defaultAlloc) + log := testlog.Logger(t, log.LvlInfo) + miner, engine, sequencer := setupSequencerTest(t, sd, log) + + _, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) + + batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{ + MinL1TxSize: 0, + MaxL1TxSize: 40_000, // try a small batch size, to force the data to be split between more frames + BatcherKey: dp.Secrets.Batcher, + }, sequencer.RollupClient(), miner.EthClient(), engine.EthClient()) + + sequencer.ActL2PipelineFull(t) + + verifier.ActL2PipelineFull(t) + cl := engine.EthClient() + + batcherNonce := uint64(0) // manually track batcher nonce. the "pending nonce" value in tx-pool is incorrect after we fill the pending-block gas limit and keep adding txs to the pool. + batcherTxOpts := func(tx *types.DynamicFeeTx) { + tx.Nonce = batcherNonce + batcherNonce++ + tx.GasFeeCap = e2eutils.Ether(1) // be very generous with basefee, since we're spamming L1 + } + + // build many L2 blocks filled to the brim with large txs of random data + for i := 0; i < 100; i++ { + aliceNonce, err := cl.PendingNonceAt(t.Ctx(), dp.Addresses.Alice) + status := sequencer.SyncStatus() + // build empty L1 blocks as necessary, so the L2 sequencer can continue to include txs while not drifting too far out + if status.UnsafeL2.Time >= status.HeadL1.Time+12 { + miner.ActEmptyBlock(t) + } + sequencer.ActL1HeadSignal(t) + sequencer.ActL2StartBlock(t) + baseFee := engine.l2Chain.CurrentBlock().BaseFee() // this will go quite high, since so many consecutive blocks are filled at capacity. + // fill the block with large L2 txs from alice + for n := aliceNonce; ; n++ { + require.NoError(t, err) + signer := types.LatestSigner(sd.L2Cfg.Config) + data := make([]byte, 120_000) // very large L2 txs, as large as the tx-pool will accept + _, err := rand.Read(data[:]) // fill with random bytes, to make compression ineffective + require.NoError(t, err) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + require.NoError(t, err) + if gas > engine.l2GasPool.Gas() { + break + } + tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ + ChainID: sd.L2Cfg.Config.ChainID, + Nonce: n, + GasTipCap: big.NewInt(2 * params.GWei), + GasFeeCap: new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), big.NewInt(2*params.GWei)), + Gas: gas, + To: &dp.Addresses.Bob, + Value: big.NewInt(0), + Data: data, + }) + require.NoError(gt, cl.SendTransaction(t.Ctx(), tx)) + engine.ActL2IncludeTx(dp.Addresses.Alice)(t) + } + sequencer.ActL2EndBlock(t) + for batcher.l2BufferedBlock.Number < sequencer.SyncStatus().UnsafeL2.Number { + // if we run out of space, close the channel and submit all the txs + if err := batcher.Buffer(t); errors.Is(err, derive.ErrTooManyRLPBytes) { + log.Info("flushing filled channel to batch txs", "id", batcher.l2ChannelOut.ID()) + batcher.ActL2ChannelClose(t) + for batcher.l2ChannelOut != nil { + batcher.ActL2BatchSubmit(t, batcherTxOpts) + } + } + } + } + + // if anything is left in the channel, submit it + if batcher.l2ChannelOut != nil { + log.Info("flushing trailing channel to batch txs", "id", batcher.l2ChannelOut.ID()) + batcher.ActL2ChannelClose(t) + for batcher.l2ChannelOut != nil { + batcher.ActL2BatchSubmit(t, batcherTxOpts) + } + } + + // build L1 blocks until we're out of txs + txs, _ := miner.eth.TxPool().ContentFrom(dp.Addresses.Batcher) + for { + if len(txs) == 0 { + break + } + miner.ActL1StartBlock(12)(t) + for range txs { + if len(txs) == 0 { + break + } + tx := txs[0] + if miner.l1GasPool.Gas() < tx.Gas() { // fill the L1 block with batcher txs until we run out of gas + break + } + log.Info("including batcher tx", "nonce", tx) + miner.IncludeTx(t, tx) + txs = txs[1:] + } + miner.ActL1EndBlock(t) + } + verifier.ActL1HeadSignal(t) + verifier.ActL2PipelineFull(t) + require.Equal(t, sequencer.SyncStatus().UnsafeL2, verifier.SyncStatus().SafeL2, "verifier synced sequencer data even though of huge tx in block") +} From cee29977722fd5351359a0f647188f3442d5c281 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Thu, 16 Mar 2023 18:30:33 -0400 Subject: [PATCH 047/404] fix(ctb): Correct env var name for mainnet config --- packages/contracts-bedrock/hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/hardhat.config.ts b/packages/contracts-bedrock/hardhat.config.ts index 16ddca942..c8a09bd76 100644 --- a/packages/contracts-bedrock/hardhat.config.ts +++ b/packages/contracts-bedrock/hardhat.config.ts @@ -23,7 +23,7 @@ const config: HardhatUserConfig = { live: false, }, mainnet: { - url: process.env.RPC_URL || 'http://localhost:8545', + url: process.env.L1_RPC || 'http://localhost:8545', }, devnetL1: { live: false, From 151219be619110cf4f33492bbc1c96aae9421c54 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Thu, 9 Mar 2023 18:54:18 -0800 Subject: [PATCH 048/404] op-e2e: bridge erc20 test Adds a test for bridging an ERC20 token through the L1StandardBridge --- op-e2e/bridge_test.go | 115 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 op-e2e/bridge_test.go diff --git a/op-e2e/bridge_test.go b/op-e2e/bridge_test.go new file mode 100644 index 000000000..10834af82 --- /dev/null +++ b/op-e2e/bridge_test.go @@ -0,0 +1,115 @@ +package op_e2e + +import ( + "math" + "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-optimism/optimism/op-node/testlog" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +// TestERC20BridgeDeposits tests the the L1StandardBridge bridge ERC20 +// functionality. +func TestERC20BridgeDeposits(t *testing.T) { + parallel(t) + if !verboseGethNodes { + log.Root().SetHandler(log.DiscardHandler()) + } + + cfg := DefaultSystemConfig(t) + + 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"] + l2Client := sys.Clients["sequencer"] + + opts, err := bind.NewKeyedTransactorWithChainID(sys.cfg.Secrets.Alice, cfg.L1ChainIDBig()) + require.Nil(t, err) + + // Deploy WETH9 + weth9Address, tx, WETH9, err := bindings.DeployWETH9(opts, l1Client) + _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) + require.Nil(t, err, "Waiting for deposit tx on L1") + + // Get some WETH + opts.Value = big.NewInt(params.Ether) + tx, err = WETH9.Fallback(opts, []byte{}) + require.Nil(t, err) + _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) + require.Nil(t, err) + opts.Value = nil + wethBalance, err := WETH9.BalanceOf(&bind.CallOpts{}, opts.From) + require.Equal(t, big.NewInt(params.Ether), wethBalance) + + // Deploy L2 WETH9 + l2Opts, err := bind.NewKeyedTransactorWithChainID(sys.cfg.Secrets.Alice, cfg.L2ChainIDBig()) + require.Nil(t, err) + optimismMintableTokenFactory, err := bindings.NewOptimismMintableERC20Factory(predeploys.OptimismMintableERC20FactoryAddr, l2Client) + require.Nil(t, err) + tx, err = optimismMintableTokenFactory.CreateOptimismMintableERC20(l2Opts, weth9Address, "L2-WETH", "L2-WETH") + _, err = waitForTransaction(tx.Hash(), l2Client, 3*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + + // Get the deployment event to have access to the L2 WETH9 address + it, err := optimismMintableTokenFactory.FilterOptimismMintableERC20Created(&bind.FilterOpts{Start: 0}, nil, nil) + require.Nil(t, err) + var event *bindings.OptimismMintableERC20FactoryOptimismMintableERC20Created + for it.Next() { + event = it.Event + } + require.NotNil(t, event) + + // Approve WETH9 with the bridge + tx, err = WETH9.Approve(opts, predeploys.DevL1StandardBridgeAddr, new(big.Int).SetUint64(math.MaxUint64)) + require.Nil(t, err) + _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) + require.Nil(t, err) + + // Bridge the WETH9 + l1StandardBridge, err := bindings.NewL1StandardBridge(predeploys.DevL1StandardBridgeAddr, l1Client) + require.Nil(t, err) + tx, err = l1StandardBridge.BridgeERC20(opts, weth9Address, event.LocalToken, big.NewInt(100), 100000, []byte{}) + require.Nil(t, err) + depositReceipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) + require.Nil(t, err) + + t.Log("Deposit through L1StandardBridge", "gas used", depositReceipt.GasUsed) + + // compute the deposit transaction hash + poll for it + portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client) + require.Nil(t, err) + + depIt, err := portal.FilterTransactionDeposited(&bind.FilterOpts{Start: 0}, nil, nil, nil) + require.Nil(t, err) + var depositEvent *bindings.OptimismPortalTransactionDeposited + for depIt.Next() { + depositEvent = depIt.Event + } + require.NotNil(t, depositEvent) + + depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) + require.Nil(t, err) + _, err = waitForTransaction(types.NewTx(depositTx).Hash(), l2Client, 3*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + + // Ensure that the deposit went through + optimismMintableToken, err := bindings.NewOptimismMintableERC20(event.LocalToken, l2Client) + require.Nil(t, err) + + // Should have balance on L2 + l2Balance, err := optimismMintableToken.BalanceOf(&bind.CallOpts{}, opts.From) + require.Nil(t, err) + require.Equal(t, l2Balance, big.NewInt(100)) +} From 9914d8c4c045b34aef658cfefcc0c1a02bbf7c6d Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Thu, 9 Mar 2023 18:58:29 -0800 Subject: [PATCH 049/404] lint: fix --- op-e2e/bridge_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/op-e2e/bridge_test.go b/op-e2e/bridge_test.go index 10834af82..51051fd23 100644 --- a/op-e2e/bridge_test.go +++ b/op-e2e/bridge_test.go @@ -42,6 +42,7 @@ func TestERC20BridgeDeposits(t *testing.T) { // Deploy WETH9 weth9Address, tx, WETH9, err := bindings.DeployWETH9(opts, l1Client) + require.NotNil(t, err) _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) require.Nil(t, err, "Waiting for deposit tx on L1") @@ -53,6 +54,7 @@ func TestERC20BridgeDeposits(t *testing.T) { require.Nil(t, err) opts.Value = nil wethBalance, err := WETH9.BalanceOf(&bind.CallOpts{}, opts.From) + require.NotNil(t, err) require.Equal(t, big.NewInt(params.Ether), wethBalance) // Deploy L2 WETH9 @@ -61,7 +63,9 @@ func TestERC20BridgeDeposits(t *testing.T) { optimismMintableTokenFactory, err := bindings.NewOptimismMintableERC20Factory(predeploys.OptimismMintableERC20FactoryAddr, l2Client) require.Nil(t, err) tx, err = optimismMintableTokenFactory.CreateOptimismMintableERC20(l2Opts, weth9Address, "L2-WETH", "L2-WETH") + require.NotNil(t, err) _, err = waitForTransaction(tx.Hash(), l2Client, 3*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + require.NotNil(t, err) // Get the deployment event to have access to the L2 WETH9 address it, err := optimismMintableTokenFactory.FilterOptimismMintableERC20Created(&bind.FilterOpts{Start: 0}, nil, nil) From 78c31ae098389b43ce0e03b5f75994d6f9bd3ce3 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Thu, 16 Mar 2023 18:18:58 -0700 Subject: [PATCH 050/404] op-e2e: better assertions --- op-e2e/bridge_test.go | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/op-e2e/bridge_test.go b/op-e2e/bridge_test.go index 51051fd23..d3d65a591 100644 --- a/op-e2e/bridge_test.go +++ b/op-e2e/bridge_test.go @@ -42,34 +42,34 @@ func TestERC20BridgeDeposits(t *testing.T) { // Deploy WETH9 weth9Address, tx, WETH9, err := bindings.DeployWETH9(opts, l1Client) - require.NotNil(t, err) + require.NoError(t, err) _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) - require.Nil(t, err, "Waiting for deposit tx on L1") + require.NoError(t, err, "Waiting for deposit tx on L1") // Get some WETH opts.Value = big.NewInt(params.Ether) tx, err = WETH9.Fallback(opts, []byte{}) - require.Nil(t, err) + require.NoError(t, err) _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) - require.Nil(t, err) + require.NoError(t, err) opts.Value = nil wethBalance, err := WETH9.BalanceOf(&bind.CallOpts{}, opts.From) - require.NotNil(t, err) + require.NoError(t, err) require.Equal(t, big.NewInt(params.Ether), wethBalance) // Deploy L2 WETH9 l2Opts, err := bind.NewKeyedTransactorWithChainID(sys.cfg.Secrets.Alice, cfg.L2ChainIDBig()) - require.Nil(t, err) + require.NoError(t, err) optimismMintableTokenFactory, err := bindings.NewOptimismMintableERC20Factory(predeploys.OptimismMintableERC20FactoryAddr, l2Client) - require.Nil(t, err) + require.NoError(t, err) tx, err = optimismMintableTokenFactory.CreateOptimismMintableERC20(l2Opts, weth9Address, "L2-WETH", "L2-WETH") - require.NotNil(t, err) + require.NoError(t, err) _, err = waitForTransaction(tx.Hash(), l2Client, 3*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) - require.NotNil(t, err) + require.NoError(t, err) // Get the deployment event to have access to the L2 WETH9 address it, err := optimismMintableTokenFactory.FilterOptimismMintableERC20Created(&bind.FilterOpts{Start: 0}, nil, nil) - require.Nil(t, err) + require.NoError(t, err) var event *bindings.OptimismMintableERC20FactoryOptimismMintableERC20Created for it.Next() { event = it.Event @@ -78,26 +78,26 @@ func TestERC20BridgeDeposits(t *testing.T) { // Approve WETH9 with the bridge tx, err = WETH9.Approve(opts, predeploys.DevL1StandardBridgeAddr, new(big.Int).SetUint64(math.MaxUint64)) - require.Nil(t, err) + require.NoError(t, err) _, err = waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) - require.Nil(t, err) + require.NoError(t, err) // Bridge the WETH9 l1StandardBridge, err := bindings.NewL1StandardBridge(predeploys.DevL1StandardBridgeAddr, l1Client) - require.Nil(t, err) + require.NoError(t, err) tx, err = l1StandardBridge.BridgeERC20(opts, weth9Address, event.LocalToken, big.NewInt(100), 100000, []byte{}) - require.Nil(t, err) + require.NoError(t, err) depositReceipt, err := waitForTransaction(tx.Hash(), l1Client, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) - require.Nil(t, err) + require.NoError(t, err) t.Log("Deposit through L1StandardBridge", "gas used", depositReceipt.GasUsed) // compute the deposit transaction hash + poll for it portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client) - require.Nil(t, err) + require.NoError(t, err) depIt, err := portal.FilterTransactionDeposited(&bind.FilterOpts{Start: 0}, nil, nil, nil) - require.Nil(t, err) + require.NoError(t, err) var depositEvent *bindings.OptimismPortalTransactionDeposited for depIt.Next() { depositEvent = depIt.Event @@ -105,15 +105,16 @@ func TestERC20BridgeDeposits(t *testing.T) { require.NotNil(t, depositEvent) depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw) - require.Nil(t, err) + require.NoError(t, err) _, err = waitForTransaction(types.NewTx(depositTx).Hash(), l2Client, 3*time.Duration(cfg.DeployConfig.L2BlockTime)*time.Second) + require.NoError(t, err) // Ensure that the deposit went through optimismMintableToken, err := bindings.NewOptimismMintableERC20(event.LocalToken, l2Client) - require.Nil(t, err) + require.NoError(t, err) // Should have balance on L2 l2Balance, err := optimismMintableToken.BalanceOf(&bind.CallOpts{}, opts.From) - require.Nil(t, err) + require.NoError(t, err) require.Equal(t, l2Balance, big.NewInt(100)) } From 25f8c132671f122749ec0191ed7c9e47ce649809 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Thu, 16 Mar 2023 17:28:30 -0400 Subject: [PATCH 051/404] fix(ci): reintroduce changesets patch Reintroduces a patch to the changesets cli that was required for private packages to be released and tagged properly. --- patches/@changesets+cli+2.26.0.patch | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 patches/@changesets+cli+2.26.0.patch diff --git a/patches/@changesets+cli+2.26.0.patch b/patches/@changesets+cli+2.26.0.patch new file mode 100644 index 000000000..9421a834c --- /dev/null +++ b/patches/@changesets+cli+2.26.0.patch @@ -0,0 +1,66 @@ +diff --git a/node_modules/@changesets/cli/dist/cli.cjs.dev.js b/node_modules/@changesets/cli/dist/cli.cjs.dev.js +index b158219..6fdfb6e 100644 +--- a/node_modules/@changesets/cli/dist/cli.cjs.dev.js ++++ b/node_modules/@changesets/cli/dist/cli.cjs.dev.js +@@ -937,7 +937,7 @@ async function publishPackages({ + }) { + const packagesByName = new Map(packages.map(x => [x.packageJson.name, x])); + const publicPackages = packages.filter(pkg => !pkg.packageJson.private); +- const unpublishedPackagesInfo = await getUnpublishedPackages(publicPackages, preState); ++ const unpublishedPackagesInfo = await getUnpublishedPackages(packages, preState); + + if (unpublishedPackagesInfo.length === 0) { + return []; +@@ -957,20 +957,27 @@ async function publishAPackage(pkg, access, twoFactorState, tag) { + const { + name, + version, +- publishConfig ++ publishConfig, ++ private: isPrivate + } = pkg.packageJson; + const localAccess = publishConfig === null || publishConfig === void 0 ? void 0 : publishConfig.access; +- logger.info(`Publishing ${chalk__default['default'].cyan(`"${name}"`)} at ${chalk__default['default'].green(`"${version}"`)}`); +- const publishDir = publishConfig !== null && publishConfig !== void 0 && publishConfig.directory ? path.join(pkg.dir, publishConfig.directory) : pkg.dir; +- const publishConfirmation = await publish(name, { +- cwd: publishDir, +- access: localAccess || access, +- tag +- }, twoFactorState); ++ let published; ++ if (!isPrivate) { ++ logger.info(`Publishing ${chalk__default['default'].cyan(`"${name}"`)} at ${chalk__default['default'].green(`"${version}"`)}`); ++ const publishDir = publishConfig !== null && publishConfig !== void 0 && publishConfig.directory ? path.join(pkg.dir, publishConfig.directory) : pkg.dir; ++ const publishConfirmation = await publish(name, { ++ cwd: publishDir, ++ access: localAccess || access, ++ tag ++ }, twoFactorState); ++ published = publishConfirmation.published; ++ } else { ++ published = true; ++ } + return { + name, + newVersion: version, +- published: publishConfirmation.published ++ published + }; + } + +@@ -1140,8 +1147,13 @@ async function tagPublish(tool, packageReleases, cwd) { + if (tool !== "root") { + for (const pkg of packageReleases) { + const tag = `${pkg.name}@${pkg.newVersion}`; +- logger.log("New tag: ", tag); +- await git.tag(tag, cwd); ++ const allTags = await git.getAllTags(cwd); ++ if (allTags.has(tag)) { ++ logger.log("Skipping existing tag: ", tag); ++ } else { ++ logger.log("New tag: ", tag); ++ await git.tag(tag, cwd); ++ } + } + } else { + const tag = `v${packageReleases[0].newVersion}`; From ee52c43c4ac7dc8f3c7ceeafaced6f80c477abff Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Fri, 17 Mar 2023 00:07:53 -0400 Subject: [PATCH 052/404] fix(docs): minor typo in contributing title Contribute to OP Stack should say Contribute to the OP Stack. --- docs/op-stack/src/docs/contribute.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/op-stack/src/docs/contribute.md b/docs/op-stack/src/docs/contribute.md index 7516d4dd6..8a5d41a5a 100644 --- a/docs/op-stack/src/docs/contribute.md +++ b/docs/op-stack/src/docs/contribute.md @@ -1,5 +1,5 @@ --- -title: Contribute to OP Stack +title: Contribute to the OP Stack lang: en-US --- @@ -21,7 +21,7 @@ The OP Stack needs YOU (yes you!) to help review the codebase for bugs and vulne ## Documentation help -Spot a typo in these docs? See something that could be a little clearer? Head over to the Optimism Monorepo where the OP Stack docs are hosted and make a pull request. No contribution is too small! +Spot a typo in these docs? See something that could be a little clearer? Head over to the Optimism Monorepo where the OP Stack docs are hosted and make a pull request. No contribution is too small! ## Community contributions @@ -30,4 +30,4 @@ If you’re looking for other ways to get involved, here are a few options: - Grab an idea from the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) to and building - Suggest a new idea for the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) - Improve the [Optimism Community Hub](https://community.optimism.io/) [documentation](https://github.com/ethereum-optimism/community-hub) or [tutorials](https://github.com/ethereum-optimism/optimism-tutorial) -- Become an Optimism Ambassador, Support Nerd, and more in the [Optimism Discord](https://discord-gateway.optimism.io/) \ No newline at end of file +- Become an Optimism Ambassador, Support Nerd, and more in the [Optimism Discord](https://discord-gateway.optimism.io/) From dbe5eb3084cb6f2b4cb992b4922ca8393eeb05ae Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Fri, 17 Mar 2023 00:11:22 -0400 Subject: [PATCH 053/404] fix(ci): add changesets for packages to rerelease Adds changesets for several packages that need to be re-released because they were incorrectly not released due to a bug in the release process. --- .changeset/tricky-donkeys-lick.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/tricky-donkeys-lick.md diff --git a/.changeset/tricky-donkeys-lick.md b/.changeset/tricky-donkeys-lick.md new file mode 100644 index 000000000..99e2591b5 --- /dev/null +++ b/.changeset/tricky-donkeys-lick.md @@ -0,0 +1,9 @@ +--- +'@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. From 3ad0d6f25a724f5c0e57e393a4faed60ac895c53 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 17 Mar 2023 14:34:44 +1000 Subject: [PATCH 054/404] op-e2e: Adjust for updated op-geth API changes --- op-e2e/actions/blocktime_test.go | 4 ++-- op-e2e/actions/fork_test.go | 4 ++-- op-e2e/actions/l1_miner_test.go | 8 +++++--- op-e2e/actions/l1_replica.go | 18 +++++++++--------- op-e2e/actions/l1_replica_test.go | 6 +++--- op-e2e/actions/l2_batcher_test.go | 6 +++--- op-e2e/actions/l2_engine_api.go | 16 ++++++++-------- op-e2e/actions/l2_engine_test.go | 12 ++++++------ op-e2e/actions/l2_sequencer_test.go | 6 +++--- op-e2e/actions/l2_verifier_test.go | 8 ++++---- op-e2e/actions/system_config_test.go | 14 +++++++------- op-e2e/actions/user_test.go | 2 +- op-e2e/geth.go | 4 ++-- 13 files changed, 55 insertions(+), 53 deletions(-) diff --git a/op-e2e/actions/blocktime_test.go b/op-e2e/actions/blocktime_test.go index 6e5fef0c0..6e160988d 100644 --- a/op-e2e/actions/blocktime_test.go +++ b/op-e2e/actions/blocktime_test.go @@ -35,7 +35,7 @@ func TestBatchInLastPossibleBlocks(gt *testing.T) { ChainID: sd.L2Cfg.Config.ChainID, Nonce: n, 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, To: &dp.Addresses.Bob, Value: e2eutils.Ether(2), @@ -146,7 +146,7 @@ func TestLargeL1Gaps(gt *testing.T) { ChainID: sd.L2Cfg.Config.ChainID, Nonce: n, 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, To: &dp.Addresses.Bob, Value: e2eutils.Ether(2), diff --git a/op-e2e/actions/fork_test.go b/op-e2e/actions/fork_test.go index 7a2404797..02d9e3d23 100644 --- a/op-e2e/actions/fork_test.go +++ b/op-e2e/actions/fork_test.go @@ -21,7 +21,7 @@ func TestShapellaL1Fork(gt *testing.T) { _, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log) - require.False(t, sd.L1Cfg.Config.IsShanghai(miner.l1Chain.CurrentBlock().Time()), "not active yet") + require.False(t, sd.L1Cfg.Config.IsShanghai(miner.l1Chain.CurrentBlock().Time), "not active yet") // start op-nodes sequencer.ActL2PipelineFull(t) @@ -34,7 +34,7 @@ func TestShapellaL1Fork(gt *testing.T) { // verify Shanghai is active l1Head := miner.l1Chain.CurrentBlock() - require.True(t, sd.L1Cfg.Config.IsShanghai(l1Head.Time())) + require.True(t, sd.L1Cfg.Config.IsShanghai(l1Head.Time)) // build L2 chain up to and including L2 blocks referencing shanghai L1 blocks sequencer.ActL1HeadSignal(t) diff --git a/op-e2e/actions/l1_miner_test.go b/op-e2e/actions/l1_miner_test.go index c5074ea4d..2d2f259ed 100644 --- a/op-e2e/actions/l1_miner_test.go +++ b/op-e2e/actions/l1_miner_test.go @@ -31,7 +31,7 @@ func TestL1Miner_BuildBlock(gt *testing.T) { ChainID: sd.L1Cfg.Config.ChainID, Nonce: 0, 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, To: &dp.Addresses.Bob, Value: e2eutils.Ether(2), @@ -41,7 +41,8 @@ func TestL1Miner_BuildBlock(gt *testing.T) { // make an empty block, even though a tx may be waiting miner.ActL1StartBlock(10)(t) miner.ActL1EndBlock(t) - bl := miner.l1Chain.CurrentBlock() + header := miner.l1Chain.CurrentBlock() + bl := miner.l1Chain.GetBlockByHash(header.Hash()) require.Equal(t, uint64(1), bl.NumberU64()) require.Zero(gt, bl.Transactions().Len()) @@ -49,7 +50,8 @@ func TestL1Miner_BuildBlock(gt *testing.T) { miner.ActL1StartBlock(10)(t) miner.ActL1IncludeTx(dp.Addresses.Alice)(t) miner.ActL1EndBlock(t) - bl = miner.l1Chain.CurrentBlock() + header = miner.l1Chain.CurrentBlock() + bl = miner.l1Chain.GetBlockByHash(header.Hash()) require.Equal(t, uint64(2), bl.NumberU64()) require.Equal(t, 1, bl.Transactions().Len()) require.Equal(t, tx.Hash(), bl.Transactions()[0].Hash()) diff --git a/op-e2e/actions/l1_replica.go b/op-e2e/actions/l1_replica.go index fd1562c0b..81c217688 100644 --- a/op-e2e/actions/l1_replica.go +++ b/op-e2e/actions/l1_replica.go @@ -103,9 +103,9 @@ func (s *L1Replica) ActL1RewindDepth(depth uint64) Action { t.InvalidAction("cannot rewind L1 past genesis (current: %d, rewind depth: %d)", head, depth) return } - finalized := s.l1Chain.CurrentFinalizedBlock() - if finalized != nil && head < finalized.NumberU64()+depth { - t.InvalidAction("cannot rewind head of chain past finalized block %d with rewind depth %d", finalized.NumberU64(), depth) + finalized := s.l1Chain.CurrentFinalBlock() + if finalized != nil && head < finalized.Number.Uint64()+depth { + t.InvalidAction("cannot rewind head of chain past finalized block %d with rewind depth %d", finalized.Number.Uint64(), depth) return } if err := s.l1Chain.SetHead(head - depth); err != nil { @@ -188,7 +188,7 @@ func (s *L1Replica) UnsafeNum() uint64 { head := s.l1Chain.CurrentBlock() headNum := uint64(0) if head != nil { - headNum = head.NumberU64() + headNum = head.Number.Uint64() } return headNum } @@ -197,16 +197,16 @@ func (s *L1Replica) SafeNum() uint64 { safe := s.l1Chain.CurrentSafeBlock() safeNum := uint64(0) if safe != nil { - safeNum = safe.NumberU64() + safeNum = safe.Number.Uint64() } return safeNum } func (s *L1Replica) FinalizedNum() uint64 { - finalized := s.l1Chain.CurrentFinalizedBlock() + finalized := s.l1Chain.CurrentFinalBlock() finalizedNum := uint64(0) if finalized != nil { - finalizedNum = finalized.NumberU64() + finalizedNum = finalized.Number.Uint64() } return finalizedNum } @@ -219,7 +219,7 @@ func (s *L1Replica) ActL1Finalize(t Testing, num uint64) { t.InvalidAction("need to move forward safe block before moving finalized block") return } - newFinalized := s.l1Chain.GetBlockByNumber(num) + newFinalized := s.l1Chain.GetHeaderByNumber(num) if newFinalized == nil { t.Fatalf("expected block at %d after finalized L1 block %d, safe head is ahead", num, finalizedNum) } @@ -234,7 +234,7 @@ func (s *L1Replica) ActL1FinalizeNext(t Testing) { // ActL1Safe marks the given unsafe block as safe. func (s *L1Replica) ActL1Safe(t Testing, num uint64) { - newSafe := s.l1Chain.GetBlockByNumber(num) + newSafe := s.l1Chain.GetHeaderByNumber(num) if newSafe == nil { t.InvalidAction("could not find L1 block %d, cannot label it as safe", num) return diff --git a/op-e2e/actions/l1_replica_test.go b/op-e2e/actions/l1_replica_test.go index 6205d5326..b813e2748 100644 --- a/op-e2e/actions/l1_replica_test.go +++ b/op-e2e/actions/l1_replica_test.go @@ -85,7 +85,7 @@ func TestL1Replica_ActL1Sync(gt *testing.T) { }) syncFromA := replica1.ActL1Sync(canonL1(chainA)) // sync canonical chain A - for replica1.l1Chain.CurrentBlock().NumberU64()+1 < uint64(len(chainA)) { + for replica1.l1Chain.CurrentBlock().Number.Uint64()+1 < uint64(len(chainA)) { syncFromA(t) } require.Equal(t, replica1.l1Chain.CurrentBlock().Hash(), chainA[len(chainA)-1].Hash(), "sync replica1 to head of chain A") @@ -94,7 +94,7 @@ func TestL1Replica_ActL1Sync(gt *testing.T) { // sync new canonical chain B syncFromB := replica1.ActL1Sync(canonL1(chainB)) - for replica1.l1Chain.CurrentBlock().NumberU64()+1 < uint64(len(chainB)) { + for replica1.l1Chain.CurrentBlock().Number.Uint64()+1 < uint64(len(chainB)) { syncFromB(t) } require.Equal(t, replica1.l1Chain.CurrentBlock().Hash(), chainB[len(chainB)-1].Hash(), "sync replica1 to head of chain B") @@ -105,7 +105,7 @@ func TestL1Replica_ActL1Sync(gt *testing.T) { _ = replica2.Close() }) syncFromOther := replica2.ActL1Sync(replica1.CanonL1Chain()) - for replica2.l1Chain.CurrentBlock().NumberU64()+1 < uint64(len(chainB)) { + for replica2.l1Chain.CurrentBlock().Number.Uint64()+1 < uint64(len(chainB)) { syncFromOther(t) } require.Equal(t, replica2.l1Chain.CurrentBlock().Hash(), chainB[len(chainB)-1].Hash(), "sync replica2 to head of chain B") diff --git a/op-e2e/actions/l2_batcher_test.go b/op-e2e/actions/l2_batcher_test.go index 1038bb1e3..91df669ed 100644 --- a/op-e2e/actions/l2_batcher_test.go +++ b/op-e2e/actions/l2_batcher_test.go @@ -48,7 +48,7 @@ func TestBatcher(gt *testing.T) { ChainID: sd.L2Cfg.Config.ChainID, Nonce: n, 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, To: &dp.Addresses.Bob, Value: e2eutils.Ether(2), @@ -73,7 +73,7 @@ func TestBatcher(gt *testing.T) { miner.ActL1IncludeTx(dp.Addresses.Batcher)(t) miner.ActL1EndBlock(t) bl := miner.l1Chain.CurrentBlock() - log.Info("bl", "txs", len(bl.Transactions())) + log.Info("bl", "txs", len(miner.l1Chain.GetBlockByHash(bl.Hash()).Transactions())) // Now make enough L1 blocks that the verifier will have to derive a L2 block // It will also eagerly derive the block from the batcher @@ -437,7 +437,7 @@ func TestBigL2Txs(gt *testing.T) { } sequencer.ActL1HeadSignal(t) sequencer.ActL2StartBlock(t) - baseFee := engine.l2Chain.CurrentBlock().BaseFee() // this will go quite high, since so many consecutive blocks are filled at capacity. + baseFee := engine.l2Chain.CurrentBlock().BaseFee // this will go quite high, since so many consecutive blocks are filled at capacity. // fill the block with large L2 txs from alice for n := aliceNonce; ; n++ { require.NoError(t, err) diff --git a/op-e2e/actions/l2_engine_api.go b/op-e2e/actions/l2_engine_api.go index 96f212745..1555f775e 100644 --- a/op-e2e/actions/l2_engine_api.go +++ b/op-e2e/actions/l2_engine_api.go @@ -202,30 +202,30 @@ func (ea *L2EngineAPI) ForkchoiceUpdatedV1(ctx context.Context, state *eth.Forkc // chain final and completely in PoS mode. if state.FinalizedBlockHash != (common.Hash{}) { // If the finalized block is not in our canonical tree, somethings wrong - finalBlock := ea.l2Chain.GetBlockByHash(state.FinalizedBlockHash) - if finalBlock == nil { + finalHeader := ea.l2Chain.GetHeaderByHash(state.FinalizedBlockHash) + if finalHeader == nil { ea.log.Warn("Final block not available in database", "hash", state.FinalizedBlockHash) return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database")) - } else if rawdb.ReadCanonicalHash(ea.l2Database, finalBlock.NumberU64()) != state.FinalizedBlockHash { + } else if rawdb.ReadCanonicalHash(ea.l2Database, finalHeader.Number.Uint64()) != state.FinalizedBlockHash { ea.log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", state.HeadBlockHash) return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain")) } // Set the finalized block - ea.l2Chain.SetFinalized(finalBlock) + ea.l2Chain.SetFinalized(finalHeader) } // Check if the safe block hash is in our canonical tree, if not somethings wrong if state.SafeBlockHash != (common.Hash{}) { - safeBlock := ea.l2Chain.GetBlockByHash(state.SafeBlockHash) - if safeBlock == nil { + safeHeader := ea.l2Chain.GetHeaderByHash(state.SafeBlockHash) + if safeHeader == nil { ea.log.Warn("Safe block not available in database") return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database")) } - if rawdb.ReadCanonicalHash(ea.l2Database, safeBlock.NumberU64()) != state.SafeBlockHash { + if rawdb.ReadCanonicalHash(ea.l2Database, safeHeader.Number.Uint64()) != state.SafeBlockHash { ea.log.Warn("Safe block not in canonical chain") return STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) } // Set the safe block - ea.l2Chain.SetSafe(safeBlock) + ea.l2Chain.SetSafe(safeHeader) } // If payload generation was requested, create a new block to be potentially // sealed by the beacon client. The payload will be requested later, and we diff --git a/op-e2e/actions/l2_engine_test.go b/op-e2e/actions/l2_engine_test.go index 3f60f60b7..5418d10b1 100644 --- a/op-e2e/actions/l2_engine_test.go +++ b/op-e2e/actions/l2_engine_test.go @@ -107,7 +107,7 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) { ChainID: sd.L2Cfg.Config.ChainID, Nonce: 0, GasTipCap: big.NewInt(2 * params.GWei), - GasFeeCap: new(big.Int).Add(engine.l2Chain.CurrentBlock().BaseFee(), big.NewInt(2*params.GWei)), + GasFeeCap: new(big.Int).Add(engine.l2Chain.CurrentBlock().BaseFee, big.NewInt(2*params.GWei)), Gas: params.TxGas, To: &dp.Addresses.Bob, Value: e2eutils.Ether(2), @@ -125,7 +125,7 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) { SafeBlockHash: genesisBlock.Hash(), FinalizedBlockHash: genesisBlock.Hash(), }, ð.PayloadAttributes{ - Timestamp: eth.Uint64Quantity(parent.Time()) + 2, + Timestamp: eth.Uint64Quantity(parent.Time) + 2, PrevRandao: eth.Bytes32{}, SuggestedFeeRecipient: common.Address{'C'}, Transactions: nil, @@ -161,12 +161,12 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) { require.Equal(t, payload.BlockHash, engine.l2Chain.CurrentBlock().Hash(), "now payload is canonical") } buildBlock(false) - require.Zero(t, engine.l2Chain.CurrentBlock().Transactions().Len(), "no tx included") + require.Zero(t, engine.l2Chain.GetBlockByHash(engine.l2Chain.CurrentBlock().Hash()).Transactions().Len(), "no tx included") buildBlock(true) - require.Equal(gt, 1, engine.l2Chain.CurrentBlock().Transactions().Len(), "tx from alice is included") + require.Equal(gt, 1, engine.l2Chain.GetBlockByHash(engine.l2Chain.CurrentBlock().Hash()).Transactions().Len(), "tx from alice is included") buildBlock(false) - require.Zero(t, engine.l2Chain.CurrentBlock().Transactions().Len(), "no tx included") - require.Equal(t, uint64(3), engine.l2Chain.CurrentBlock().NumberU64(), "built 3 blocks") + require.Zero(t, engine.l2Chain.GetBlockByHash(engine.l2Chain.CurrentBlock().Hash()).Transactions().Len(), "no tx included") + require.Equal(t, uint64(3), engine.l2Chain.CurrentBlock().Number.Uint64(), "built 3 blocks") } func TestL2EngineAPIFail(gt *testing.T) { diff --git a/op-e2e/actions/l2_sequencer_test.go b/op-e2e/actions/l2_sequencer_test.go index ed1c8d51c..5a62617d7 100644 --- a/op-e2e/actions/l2_sequencer_test.go +++ b/op-e2e/actions/l2_sequencer_test.go @@ -55,7 +55,7 @@ func TestL2Sequencer_SequencerDrift(gt *testing.T) { ChainID: sd.L2Cfg.Config.ChainID, Nonce: n, 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, To: &dp.Addresses.Bob, Value: e2eutils.Ether(2), @@ -76,7 +76,7 @@ func TestL2Sequencer_SequencerDrift(gt *testing.T) { origin := miner.l1Chain.CurrentBlock() // L2 makes blocks to catch up - for sequencer.SyncStatus().UnsafeL2.Time+sd.RollupCfg.BlockTime < origin.Time() { + for sequencer.SyncStatus().UnsafeL2.Time+sd.RollupCfg.BlockTime < origin.Time { makeL2BlockWithAliceTx() require.Equal(t, uint64(0), sequencer.SyncStatus().UnsafeL2.L1Origin.Number, "no L1 origin change before time matches") } @@ -89,7 +89,7 @@ func TestL2Sequencer_SequencerDrift(gt *testing.T) { sequencer.ActL1HeadSignal(t) // Make blocks up till the sequencer drift is about to surpass, but keep the old L1 origin - for sequencer.SyncStatus().UnsafeL2.Time+sd.RollupCfg.BlockTime <= origin.Time()+sd.RollupCfg.MaxSequencerDrift { + for sequencer.SyncStatus().UnsafeL2.Time+sd.RollupCfg.BlockTime <= origin.Time+sd.RollupCfg.MaxSequencerDrift { sequencer.ActL2KeepL1Origin(t) makeL2BlockWithAliceTx() require.Equal(t, uint64(1), sequencer.SyncStatus().UnsafeL2.L1Origin.Number, "expected to keep old L1 origin") diff --git a/op-e2e/actions/l2_verifier_test.go b/op-e2e/actions/l2_verifier_test.go index 916e05c5b..c402558f8 100644 --- a/op-e2e/actions/l2_verifier_test.go +++ b/op-e2e/actions/l2_verifier_test.go @@ -41,20 +41,20 @@ func TestL2Verifier_SequenceWindow(gt *testing.T) { miner.ActL1SetFeeRecipient(common.Address{'A'}) // Make two sequence windows worth of empty L1 blocks. After we pass the first sequence window, the L2 chain should get blocks - for miner.l1Chain.CurrentBlock().NumberU64() < sd.RollupCfg.SeqWindowSize*2 { + for miner.l1Chain.CurrentBlock().Number.Uint64() < sd.RollupCfg.SeqWindowSize*2 { miner.ActL1StartBlock(10)(t) miner.ActL1EndBlock(t) verifier.ActL2PipelineFull(t) - l1Head := miner.l1Chain.CurrentBlock().NumberU64() + l1Head := miner.l1Chain.CurrentBlock().Number.Uint64() expectedL1Origin := uint64(0) // as soon as we complete the sequence window, we force-adopt the L1 origin if l1Head >= sd.RollupCfg.SeqWindowSize { expectedL1Origin = l1Head - sd.RollupCfg.SeqWindowSize } require.Equal(t, expectedL1Origin, verifier.SyncStatus().SafeL2.L1Origin.Number, "L1 origin is forced in, given enough L1 blocks pass by") - require.LessOrEqual(t, miner.l1Chain.GetBlockByNumber(expectedL1Origin).Time(), engine.l2Chain.CurrentBlock().Time(), "L2 time higher than L1 origin time") + require.LessOrEqual(t, miner.l1Chain.GetBlockByNumber(expectedL1Origin).Time(), engine.l2Chain.CurrentBlock().Time, "L2 time higher than L1 origin time") } tip2N := verifier.SyncStatus() @@ -75,7 +75,7 @@ func TestL2Verifier_SequenceWindow(gt *testing.T) { verifier.ActL2PipelineFull(t) require.Equal(t, tip2N.SafeL2, verifier.SyncStatus().SafeL2) - for miner.l1Chain.CurrentBlock().NumberU64() < sd.RollupCfg.SeqWindowSize*2 { + for miner.l1Chain.CurrentBlock().Number.Uint64() < sd.RollupCfg.SeqWindowSize*2 { miner.ActL1StartBlock(10)(t) miner.ActL1EndBlock(t) } diff --git a/op-e2e/actions/system_config_test.go b/op-e2e/actions/system_config_test.go index e4baf3d5c..018c4e0f6 100644 --- a/op-e2e/actions/system_config_test.go +++ b/op-e2e/actions/system_config_test.go @@ -80,7 +80,7 @@ func TestBatcherKeyRotation(gt *testing.T) { miner.ActL1StartBlock(12)(t) miner.ActL1IncludeTx(dp.Addresses.SysCfgOwner)(t) miner.ActL1EndBlock(t) - cfgChangeL1BlockNum := miner.l1Chain.CurrentBlock().NumberU64() + cfgChangeL1BlockNum := miner.l1Chain.CurrentBlock().Number.Uint64() // sequence L2 blocks, and submit with new batcher sequencer.ActL1HeadSignal(t) @@ -200,7 +200,7 @@ func TestGPOParamsChange(gt *testing.T) { miner.ActEmptyBlock(t) sequencer.ActL1HeadSignal(t) sequencer.ActBuildToL1Head(t) - basefee := miner.l1Chain.CurrentBlock().BaseFee() + basefee := miner.l1Chain.CurrentBlock().BaseFee // alice makes a L2 tx, sequencer includes it alice.ActResetTxOpts(t) @@ -238,7 +238,7 @@ func TestGPOParamsChange(gt *testing.T) { miner.ActL1StartBlock(12)(t) miner.ActL1IncludeTx(dp.Addresses.SysCfgOwner)(t) miner.ActL1EndBlock(t) - basefeeGPOUpdate := miner.l1Chain.CurrentBlock().BaseFee() + basefeeGPOUpdate := miner.l1Chain.CurrentBlock().BaseFee // build empty L2 chain, up to but excluding the L2 block with the L1 origin that processes the GPO change sequencer.ActL1HeadSignal(t) @@ -274,7 +274,7 @@ func TestGPOParamsChange(gt *testing.T) { // build more L2 blocks, with new L1 origin miner.ActEmptyBlock(t) - basefee = miner.l1Chain.CurrentBlock().BaseFee() + basefee = miner.l1Chain.CurrentBlock().BaseFee sequencer.ActL1HeadSignal(t) sequencer.ActBuildToL1Head(t) // and Alice makes a tx again @@ -313,7 +313,7 @@ func TestGasLimitChange(gt *testing.T) { sequencer.ActL1HeadSignal(t) sequencer.ActBuildToL1Head(t) - oldGasLimit := seqEngine.l2Chain.CurrentBlock().GasLimit() + oldGasLimit := seqEngine.l2Chain.CurrentBlock().GasLimit require.Equal(t, oldGasLimit, uint64(dp.DeployConfig.L2GenesisBlockGasLimit)) // change gas limit on L1 to triple what it was @@ -335,12 +335,12 @@ func TestGasLimitChange(gt *testing.T) { sequencer.ActL1HeadSignal(t) sequencer.ActBuildToL1HeadExcl(t) - require.Equal(t, oldGasLimit, seqEngine.l2Chain.CurrentBlock().GasLimit()) + require.Equal(t, oldGasLimit, seqEngine.l2Chain.CurrentBlock().GasLimit) require.Equal(t, uint64(1), sequencer.SyncStatus().UnsafeL2.L1Origin.Number) // now include the L1 block with the gaslimit change, and see if it changes as expected sequencer.ActBuildToL1Head(t) - require.Equal(t, oldGasLimit*3, seqEngine.l2Chain.CurrentBlock().GasLimit()) + require.Equal(t, oldGasLimit*3, seqEngine.l2Chain.CurrentBlock().GasLimit) require.Equal(t, uint64(2), sequencer.SyncStatus().UnsafeL2.L1Origin.Number) // now submit all this to L1, and see if a verifier can sync and reproduce it diff --git a/op-e2e/actions/user_test.go b/op-e2e/actions/user_test.go index 0dd75be73..fd0385b11 100644 --- a/op-e2e/actions/user_test.go +++ b/op-e2e/actions/user_test.go @@ -132,7 +132,7 @@ func runCrossLayerUserTest(gt *testing.T, test regolithScheduledTest) { seq.ActL1HeadSignal(t) // sync sequencer build enough blocks to adopt latest L1 origin - for seq.SyncStatus().UnsafeL2.L1Origin.Number < miner.l1Chain.CurrentBlock().NumberU64() { + for seq.SyncStatus().UnsafeL2.L1Origin.Number < miner.l1Chain.CurrentBlock().Number.Uint64() { seq.ActL2StartBlock(t) seq.ActL2EndBlock(t) } diff --git a/op-e2e/geth.go b/op-e2e/geth.go index 1af7be4e4..966abafb1 100644 --- a/op-e2e/geth.go +++ b/op-e2e/geth.go @@ -160,11 +160,11 @@ func (f *fakeSafeFinalizedL1) Start() error { case head := <-headChanges: num := head.Block.NumberU64() if num > f.finalizedDistance { - toFinalize := f.eth.BlockChain().GetBlockByNumber(num - f.finalizedDistance) + toFinalize := f.eth.BlockChain().GetHeaderByNumber(num - f.finalizedDistance) f.eth.BlockChain().SetFinalized(toFinalize) } if num > f.safeDistance { - toSafe := f.eth.BlockChain().GetBlockByNumber(num - f.safeDistance) + toSafe := f.eth.BlockChain().GetHeaderByNumber(num - f.safeDistance) f.eth.BlockChain().SetSafe(toSafe) } case <-quit: From b93874dd2f2def8c3fbeec133d64d2f1f6a85398 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 17 Mar 2023 14:50:02 +1000 Subject: [PATCH 055/404] op-e2e: Increase funding for test users. --- op-e2e/e2eutils/setup.go | 4 ++-- op-e2e/e2eutils/setup_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index 7fa6fe6bc..c86b47578 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -169,7 +169,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * if alloc.PrefundTestUsers { for _, addr := range deployParams.Addresses.All() { l1Genesis.Alloc[addr] = core.GenesisAccount{ - Balance: Ether(1e6), + Balance: Ether(1e12), } } } @@ -184,7 +184,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * if alloc.PrefundTestUsers { for _, addr := range deployParams.Addresses.All() { l2Genesis.Alloc[addr] = core.GenesisAccount{ - Balance: Ether(1e6), + Balance: Ether(1e12), } } } diff --git a/op-e2e/e2eutils/setup_test.go b/op-e2e/e2eutils/setup_test.go index 78cbff32d..b53c41e64 100644 --- a/op-e2e/e2eutils/setup_test.go +++ b/op-e2e/e2eutils/setup_test.go @@ -27,10 +27,10 @@ func TestSetup(t *testing.T) { alloc := &AllocParams{PrefundTestUsers: true} sd := Setup(t, dp, alloc) require.Contains(t, sd.L1Cfg.Alloc, dp.Addresses.Alice) - require.Equal(t, sd.L1Cfg.Alloc[dp.Addresses.Alice].Balance, Ether(1e6)) + require.Equal(t, sd.L1Cfg.Alloc[dp.Addresses.Alice].Balance, Ether(1e12)) require.Contains(t, sd.L2Cfg.Alloc, dp.Addresses.Alice) - require.Equal(t, sd.L2Cfg.Alloc[dp.Addresses.Alice].Balance, Ether(1e6)) + require.Equal(t, sd.L2Cfg.Alloc[dp.Addresses.Alice].Balance, Ether(1e12)) require.Contains(t, sd.L1Cfg.Alloc, predeploys.DevOptimismPortalAddr) require.Contains(t, sd.L2Cfg.Alloc, predeploys.L1BlockAddr) From 06787ad486cd5216a1d7aaa72af40b4a8d2cdc7e Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 14:26:23 -0400 Subject: [PATCH 056/404] max frame size config validation --- op-batcher/batcher/channel_builder.go | 13 +++++++++++-- op-batcher/batcher/channel_builder_test.go | 6 +++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 7bb59416f..070c4a0e9 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -12,7 +12,8 @@ import ( ) var ( - ErrInvalidMaxFrameSize = errors.New("max frame size cannot be zero") + ErrZeroMaxFrameSize = errors.New("max frame size cannot be zero") + ErrSmallMaxFrameSize = errors.New("max frame size cannot be less than 23") ErrInvalidChannelTimeout = errors.New("channel timeout is less than the safety margin") ErrInputTargetReached = errors.New("target amount of input data reached") ErrMaxFrameIndex = errors.New("max frame index reached (uint16)") @@ -82,7 +83,15 @@ func (cc *ChannelConfig) Check() error { // will infinitely loop when trying to create frames in the // [channelBuilder.OutputFrames] function. if cc.MaxFrameSize == 0 { - return ErrInvalidMaxFrameSize + return ErrZeroMaxFrameSize + } + + // If the [MaxFrameSize] is set to < 23, the channel out + // will underflow the maxSize variable in the [derive.ChannelOut]. + // Since it is of type uint64, it will wrap around to a very large + // number, making the frame size extremely large. + if cc.MaxFrameSize < 23 { + return ErrSmallMaxFrameSize } return nil diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index c63acdd31..16854a09b 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -37,7 +37,11 @@ func TestConfigValidation(t *testing.T) { // Set the config to have a zero max frame size. validChannelConfig.MaxFrameSize = 0 - require.ErrorIs(t, validChannelConfig.Check(), ErrInvalidMaxFrameSize) + require.ErrorIs(t, validChannelConfig.Check(), ErrZeroMaxFrameSize) + + // Set the config to have a max frame size less than 23. + validChannelConfig.MaxFrameSize = 22 + require.ErrorIs(t, validChannelConfig.Check(), ErrSmallMaxFrameSize) // Reset the config and test the Timeout error. // NOTE: We should be fuzzing these values with the constraint that From 835dd0ffd6001d6e73d15e208fa9114be64c1d2e Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 14:52:49 -0400 Subject: [PATCH 057/404] add maxSize check to the channel out OutputFrame :gear: --- op-node/rollup/derive/channel_out.go | 7 +++++ op-node/rollup/derive/channel_out_test.go | 36 ++++++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index feef8ae7c..a0e9c688d 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +var ErrMaxFrameSizeTooSmall = errors.New("maxSize is too small to fit the fixed frame overhead") var ErrNotDepositTx = errors.New("first transaction in block is not a deposit tx") var ErrTooManyRLPBytes = errors.New("batch would cause RLP bytes to go over limit") @@ -150,6 +151,12 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, erro FrameNumber: uint16(co.frame), } + // Error if the `maxSize` is too small to fit the fixed frame + // overhead as specified below. + if maxSize < 23 { + return 0, ErrMaxFrameSizeTooSmall + } + // Copy data from the local buffer into the frame data buffer // Don't go past the maxSize with the fixed frame overhead. // Fixed overhead: 16 + 2 + 4 + 1 = 23 bytes. diff --git a/op-node/rollup/derive/channel_out_test.go b/op-node/rollup/derive/channel_out_test.go index ea6eeac36..44fa6ee40 100644 --- a/op-node/rollup/derive/channel_out_test.go +++ b/op-node/rollup/derive/channel_out_test.go @@ -29,6 +29,26 @@ func TestChannelOutAddBlock(t *testing.T) { }) } +// TestOutputFrameSmallMaxSize tests that calling [OutputFrame] with a small +// max size that is below the fixed frame size overhead of 23, will return +// an error. +func TestOutputFrameSmallMaxSize(t *testing.T) { + cout, err := NewChannelOut() + require.NoError(t, err) + + // Set the channel out frame details + cout.id = ChannelID{0x01} + cout.frame = 0 + + // Call OutputFrame with the range of small max size values that err + w := bytes.NewBuffer([]byte{}) + for i := 0; i < 23; i++ { + fid, err := cout.OutputFrame(w, uint64(i)) + require.ErrorIs(t, err, ErrMaxFrameSizeTooSmall) + require.Zero(t, fid) + } +} + // TestRLPByteLimit ensures that stream encoder is properly limiting the length. // It will decode the input if `len(input) <= inputLimit`. func TestRLPByteLimit(t *testing.T) { @@ -64,42 +84,42 @@ func TestForceCloseTxData(t *testing.T) { output: "", }, { - frames: []Frame{Frame{FrameNumber: 0, IsLast: false}, Frame{ID: id, FrameNumber: 1, IsLast: true}}, + frames: []Frame{{FrameNumber: 0, IsLast: false}, {ID: id, FrameNumber: 1, IsLast: true}}, errors: true, output: "", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 0, IsLast: false}}, + frames: []Frame{{ID: id, FrameNumber: 0, IsLast: false}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000001", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 0, IsLast: true}}, + frames: []Frame{{ID: id, FrameNumber: 0, IsLast: true}}, errors: false, output: "00", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}}, + frames: []Frame{{ID: id, FrameNumber: 1, IsLast: false}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000001", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: true}}, + frames: []Frame{{ID: id, FrameNumber: 1, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 2, IsLast: true}}, + frames: []Frame{{ID: id, FrameNumber: 2, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00010000000000", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}, Frame{ID: id, FrameNumber: 3, IsLast: true}}, + frames: []Frame{{ID: id, FrameNumber: 1, IsLast: false}, {ID: id, FrameNumber: 3, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000", }, { - frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}, Frame{ID: id, FrameNumber: 3, IsLast: true}, Frame{ID: id, FrameNumber: 5, IsLast: true}}, + frames: []Frame{{ID: id, FrameNumber: 1, IsLast: false}, {ID: id, FrameNumber: 3, IsLast: true}, {ID: id, FrameNumber: 5, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000", }, From 589c83e16cd1d9ddd6703a779252a57088a3697a Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 17 Mar 2023 18:16:43 +0100 Subject: [PATCH 058/404] op-node: make find-sync-start more resilient against errors by improving L1 and L2 rpc caches to include full ranges of block-ref info, and adjust code to hit L1 block ref cache when possible --- op-node/rollup/derive/engine_queue_test.go | 18 +++++++++--------- op-node/rollup/sync/start.go | 13 +++++++++++-- op-node/sources/l1_client.go | 4 +++- op-node/sources/l2_client.go | 4 +++- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/op-node/rollup/derive/engine_queue_test.go b/op-node/rollup/derive/engine_queue_test.go index b4dbd7438..be22db128 100644 --- a/op-node/rollup/derive/engine_queue_test.go +++ b/op-node/rollup/derive/engine_queue_test.go @@ -216,17 +216,17 @@ func TestEngineQueue_Finalize(t *testing.T) { eng.ExpectL2BlockRefByHash(refF0.ParentHash, refE1, nil) // meet previous safe, counts 1/2 - l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) + l1F.ExpectL1BlockRefByHash(refE.Hash, refE, nil) eng.ExpectL2BlockRefByHash(refE1.ParentHash, refE0, nil) eng.ExpectL2BlockRefByHash(refE0.ParentHash, refD1, nil) // now full seq window, inclusive - l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) + l1F.ExpectL1BlockRefByHash(refD.Hash, refD, nil) eng.ExpectL2BlockRefByHash(refD1.ParentHash, refD0, nil) eng.ExpectL2BlockRefByHash(refD0.ParentHash, refC1, nil) // now one more L1 origin - l1F.ExpectL1BlockRefByNumber(refC.Number, refC, nil) + l1F.ExpectL1BlockRefByHash(refC.Hash, refC, nil) eng.ExpectL2BlockRefByHash(refC1.ParentHash, refC0, nil) // parent of that origin will be considered safe eng.ExpectL2BlockRefByHash(refC0.ParentHash, refB1, nil) @@ -450,17 +450,17 @@ func TestEngineQueue_ResetWhenUnsafeOriginNotCanonical(t *testing.T) { eng.ExpectL2BlockRefByHash(refF0.ParentHash, refE1, nil) // meet previous safe, counts 1/2 - l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) + l1F.ExpectL1BlockRefByHash(refE.Hash, refE, nil) eng.ExpectL2BlockRefByHash(refE1.ParentHash, refE0, nil) eng.ExpectL2BlockRefByHash(refE0.ParentHash, refD1, nil) // now full seq window, inclusive - l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) + l1F.ExpectL1BlockRefByHash(refD.Hash, refD, nil) eng.ExpectL2BlockRefByHash(refD1.ParentHash, refD0, nil) eng.ExpectL2BlockRefByHash(refD0.ParentHash, refC1, nil) // now one more L1 origin - l1F.ExpectL1BlockRefByNumber(refC.Number, refC, nil) + l1F.ExpectL1BlockRefByHash(refC.Hash, refC, nil) eng.ExpectL2BlockRefByHash(refC1.ParentHash, refC0, nil) // parent of that origin will be considered safe eng.ExpectL2BlockRefByHash(refC0.ParentHash, refB1, nil) @@ -782,17 +782,17 @@ func TestVerifyNewL1Origin(t *testing.T) { } // meet previous safe, counts 1/2 - l1F.ExpectL1BlockRefByNumber(refE.Number, refE, nil) + l1F.ExpectL1BlockRefByHash(refE.Hash, refE, nil) eng.ExpectL2BlockRefByHash(refE1.ParentHash, refE0, nil) eng.ExpectL2BlockRefByHash(refE0.ParentHash, refD1, nil) // now full seq window, inclusive - l1F.ExpectL1BlockRefByNumber(refD.Number, refD, nil) + l1F.ExpectL1BlockRefByHash(refD.Hash, refD, nil) eng.ExpectL2BlockRefByHash(refD1.ParentHash, refD0, nil) eng.ExpectL2BlockRefByHash(refD0.ParentHash, refC1, nil) // now one more L1 origin - l1F.ExpectL1BlockRefByNumber(refC.Number, refC, nil) + l1F.ExpectL1BlockRefByHash(refC.Hash, refC, nil) eng.ExpectL2BlockRefByHash(refC1.ParentHash, refC0, nil) // parent of that origin will be considered safe eng.ExpectL2BlockRefByHash(refC0.ParentHash, refB1, nil) diff --git a/op-node/rollup/sync/start.go b/op-node/rollup/sync/start.go index 5ade51532..de2def455 100644 --- a/op-node/rollup/sync/start.go +++ b/op-node/rollup/sync/start.go @@ -126,8 +126,17 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain // then we return the last L2 block of the epoch before that as safe head. // Each loop iteration we traverse a single L2 block, and we check if the L1 origins are consistent. for { - // Fetch L1 information if we never had it, or if we do not have it for the current origin - if l1Block == (eth.L1BlockRef{}) || n.L1Origin.Hash != l1Block.Hash { + // Fetch L1 information if we never had it, or if we do not have it for the current origin. + // Optimization: as soon as we have a previous L1 block, try to traverse L1 by hash instead of by number, to fill the cache. + if n.L1Origin.Hash == l1Block.ParentHash { + b, err := l1.L1BlockRefByHash(ctx, n.L1Origin.Hash) + if err != nil { + // Exit, find-sync start should start over, to move to an available L1 chain with block-by-number / not-found case. + return nil, fmt.Errorf("failed to retrieve L1 block: %w", err) + } + l1Block = b + ahead = false + } else if l1Block == (eth.L1BlockRef{}) || n.L1Origin.Hash != l1Block.Hash { b, err := l1.L1BlockRefByNumber(ctx, n.L1Origin.Number) // if L2 is ahead of L1 view, then consider it a "plausible" head notFound := errors.Is(err, ethereum.NotFound) diff --git a/op-node/sources/l1_client.go b/op-node/sources/l1_client.go index af224c0c9..d873e64e6 100644 --- a/op-node/sources/l1_client.go +++ b/op-node/sources/l1_client.go @@ -24,6 +24,7 @@ type L1ClientConfig struct { func L1ClientDefaultConfig(config *rollup.Config, trustRPC bool, kind RPCProviderKind) *L1ClientConfig { // Cache 3/2 worth of sequencing window of receipts and txs span := int(config.SeqWindowSize) * 3 / 2 + fullSpan := span if span > 1000 { // sanity cap. If a large sequencing window is configured, do not make the cache too large span = 1000 } @@ -40,7 +41,8 @@ func L1ClientDefaultConfig(config *rollup.Config, trustRPC bool, kind RPCProvide MustBePostMerge: false, RPCProviderKind: kind, }, - L1BlockRefsCacheSize: span, + // Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors. + L1BlockRefsCacheSize: fullSpan, } } diff --git a/op-node/sources/l2_client.go b/op-node/sources/l2_client.go index e65d660c8..d2970fa81 100644 --- a/op-node/sources/l2_client.go +++ b/op-node/sources/l2_client.go @@ -34,6 +34,7 @@ func L2ClientDefaultConfig(config *rollup.Config, trustRPC bool) *L2ClientConfig span *= 12 span /= int(config.BlockTime) } + fullSpan := span if span > 1000 { // sanity cap. If a large sequencing window is configured, do not make the cache too large span = 1000 } @@ -50,7 +51,8 @@ func L2ClientDefaultConfig(config *rollup.Config, trustRPC bool) *L2ClientConfig MustBePostMerge: true, RPCProviderKind: RPCKindBasic, }, - L2BlockRefsCacheSize: span, + // Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors. + L2BlockRefsCacheSize: fullSpan, L1ConfigsCacheSize: span, RollupCfg: config, } From 41a102594ece101ec96c0dfc091ad24b6a021302 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 15:41:41 -0400 Subject: [PATCH 059/404] fix batcher tests with maxSize check --- op-batcher/batcher/channel_builder_test.go | 82 ++++++++++------------ op-batcher/batcher/channel_manager_test.go | 2 +- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index c63acdd31..02a61e3b7 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -61,6 +61,12 @@ func addNonsenseBlock(cb *channelBuilder) error { return err } txs := []*types.Transaction{types.NewTx(l1InfoTx)} + for i := 0; i < 100; i++ { + txData := make([]byte, 32) + _, _ = rand.Read(txData) + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), txData) + txs = append(txs, tx) + } a := types.NewBlock(&types.Header{ Number: big.NewInt(0), }, txs, nil, nil, trie.NewStackTrie(nil)) @@ -366,15 +372,14 @@ func TestBuilderWrongFramePanic(t *testing.T) { }) } -// TestOutputFrames tests the OutputFrames function -func TestOutputFrames(t *testing.T) { +// TestOutputFramesHappy tests the OutputFrames function happy path +func TestOutputFramesHappy(t *testing.T) { channelConfig := defaultTestChannelConfig - channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = 24 // Construct the channel builder cb, err := newChannelBuilder(channelConfig) require.NoError(t, err) - require.False(t, cb.IsFull()) require.Equal(t, 0, cb.NumFrames()) @@ -383,35 +388,28 @@ func TestOutputFrames(t *testing.T) { require.NoError(t, cb.OutputFrames()) // There should be no ready bytes yet - readyBytes := cb.co.ReadyBytes() - require.Equal(t, 0, readyBytes) + require.Equal(t, 0, cb.co.ReadyBytes()) // Let's add a block - err = addNonsenseBlock(cb) - require.NoError(t, err) + require.NoError(t, addNonsenseBlock(cb)) - // Check how many ready bytes - readyBytes = cb.co.ReadyBytes() - require.Equal(t, 2, readyBytes) + // Force a compression flush to the output buffer + require.NoError(t, cb.co.Flush()) + // Check how many ready bytes + // There should be more than the max frame size ready + require.Greater(t, uint64(cb.co.ReadyBytes()), channelConfig.MaxFrameSize) require.Equal(t, 0, cb.NumFrames()) // The channel should not be full // but we want to output the frames for testing anyways - isFull := cb.IsFull() - require.False(t, isFull) - - // Since we manually set the max frame size to 2, - // we should be able to compress the two frames now - err = cb.OutputFrames() - require.NoError(t, err) + require.False(t, cb.IsFull()) - // There should be one frame in the channel builder now - require.Equal(t, 1, cb.NumFrames()) + // We should be able to output the frames + require.NoError(t, cb.OutputFrames()) - // There should no longer be any ready bytes - readyBytes = cb.co.ReadyBytes() - require.Equal(t, 0, readyBytes) + // There should be many frames in the channel builder now + require.Greater(t, cb.NumFrames(), 1) } // TestMaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames] @@ -435,9 +433,9 @@ func TestMaxRLPBytesPerChannel(t *testing.T) { // function errors when the max frame index is reached. func TestOutputFramesMaxFrameIndex(t *testing.T) { channelConfig := defaultTestChannelConfig - channelConfig.MaxFrameSize = 1 + channelConfig.MaxFrameSize = 24 channelConfig.TargetNumFrames = math.MaxInt - channelConfig.TargetFrameSize = 1 + channelConfig.TargetFrameSize = 24 channelConfig.ApproxComprRatio = 0 // Continuously add blocks until the max frame index is reached @@ -459,6 +457,7 @@ func TestOutputFramesMaxFrameIndex(t *testing.T) { Number: big.NewInt(0), }, txs, nil, nil, trie.NewStackTrie(nil)) err = cb.AddBlock(a) + require.NoError(t, cb.co.Flush()) if cb.IsFull() { fullErr := cb.FullErr() require.ErrorIs(t, fullErr, ErrMaxFrameIndex) @@ -476,12 +475,10 @@ func TestBuilderAddBlock(t *testing.T) { channelConfig := defaultTestChannelConfig // Lower the max frame size so that we can batch - channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = 30 // Configure the Input Threshold params so we observe a full channel - // In reality, we only need the input bytes (74) below to be greater than - // or equal to the input threshold (3 * 2) / 1 = 6 - channelConfig.TargetFrameSize = 3 + channelConfig.TargetFrameSize = 30 channelConfig.TargetNumFrames = 2 channelConfig.ApproxComprRatio = 1 @@ -490,19 +487,18 @@ func TestBuilderAddBlock(t *testing.T) { require.NoError(t, err) // Add a nonsense block to the channel builder - err = addNonsenseBlock(cb) - require.NoError(t, err) + require.NoError(t, addNonsenseBlock(cb)) + require.NoError(t, cb.co.Flush()) // Check the fields reset in the AddBlock function - require.Equal(t, 74, cb.co.InputBytes()) + require.Equal(t, 6578, cb.co.InputBytes()) require.Equal(t, 1, len(cb.blocks)) require.Equal(t, 0, len(cb.frames)) require.True(t, cb.IsFull()) // Since the channel output is full, the next call to AddBlock // should return the channel out full error - err = addNonsenseBlock(cb) - require.ErrorIs(t, err, ErrInputTargetReached) + require.ErrorIs(t, addNonsenseBlock(cb), ErrInputTargetReached) } // TestBuilderReset tests the Reset function @@ -510,14 +506,14 @@ func TestBuilderReset(t *testing.T) { channelConfig := defaultTestChannelConfig // Lower the max frame size so that we can batch - channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = 24 cb, err := newChannelBuilder(channelConfig) require.NoError(t, err) // Add a nonsense block to the channel builder - err = addNonsenseBlock(cb) - require.NoError(t, err) + require.NoError(t, addNonsenseBlock(cb)) + require.NoError(t, cb.co.Flush()) // Check the fields reset in the Reset function require.Equal(t, 1, len(cb.blocks)) @@ -528,22 +524,20 @@ func TestBuilderReset(t *testing.T) { require.NoError(t, cb.fullErr) // Output frames so we can set the channel builder frames - err = cb.OutputFrames() - require.NoError(t, err) + require.NoError(t, cb.OutputFrames()) // Add another block to increment the block count - err = addNonsenseBlock(cb) - require.NoError(t, err) + require.NoError(t, addNonsenseBlock(cb)) + require.NoError(t, cb.co.Flush()) // Check the fields reset in the Reset function require.Equal(t, 2, len(cb.blocks)) - require.Equal(t, 1, len(cb.frames)) + require.Greater(t, len(cb.frames), 1) require.Equal(t, timeout, cb.timeout) require.NoError(t, cb.fullErr) // Reset the channel builder - err = cb.Reset() - require.NoError(t, err) + require.NoError(t, cb.Reset()) // Check the fields reset in the Reset function require.Equal(t, 0, len(cb.blocks)) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 21d501d60..fb8d274ff 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -188,7 +188,7 @@ func TestClearChannelManager(t *testing.T) { ChannelTimeout: 10, // Have to set the max frame size here otherwise the channel builder would not // be able to output any frames - MaxFrameSize: 1, + MaxFrameSize: 24, }) // Channel Manager state should be empty by default From 6122ef6eb1a266ac876d0d49a5136de897b53f3d Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 15:53:10 -0400 Subject: [PATCH 060/404] fix bad rnd gen --- op-batcher/batcher/channel_manager_test.go | 71 +++++++++++----------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index fb8d274ff..2bbf07100 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -32,8 +32,7 @@ func TestPendingChannelTimeout(t *testing.T) { require.False(t, timeout) // Set the pending channel - err := m.ensurePendingChannel(eth.BlockID{}) - require.NoError(t, err) + require.NoError(t, m.ensurePendingChannel(eth.BlockID{})) // There are no confirmed transactions so // the pending channel cannot be timed out @@ -85,14 +84,10 @@ func TestChannelManagerReturnsErrReorg(t *testing.T) { ParentHash: common.Hash{0xff}, }, nil, nil, nil, nil) - err := m.AddL2Block(a) - require.NoError(t, err) - err = m.AddL2Block(b) - require.NoError(t, err) - err = m.AddL2Block(c) - require.NoError(t, err) - err = m.AddL2Block(x) - require.ErrorIs(t, err, ErrReorg) + require.NoError(t, m.AddL2Block(a)) + require.NoError(t, m.AddL2Block(b)) + require.NoError(t, m.AddL2Block(c)) + require.ErrorIs(t, m.AddL2Block(x), ErrReorg) require.Equal(t, []*types.Block{a, b, c}, m.blocks) } @@ -123,16 +118,14 @@ func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) { ParentHash: common.Hash{0xff}, }, txs, nil, nil, trie.NewStackTrie(nil)) - err = m.AddL2Block(a) - require.NoError(t, err) + require.NoError(t, m.AddL2Block(a)) _, err = m.TxData(eth.BlockID{}) require.NoError(t, err) _, err = m.TxData(eth.BlockID{}) require.ErrorIs(t, err, io.EOF) - err = m.AddL2Block(x) - require.ErrorIs(t, err, ErrReorg) + require.ErrorIs(t, m.AddL2Block(x), ErrReorg) } // TestChannelManagerNextTxData checks the nextTxData function. @@ -148,8 +141,7 @@ func TestChannelManagerNextTxData(t *testing.T) { // Set the pending channel // The nextTxData function should still return EOF // since the pending channel has no frames - err = m.ensurePendingChannel(eth.BlockID{}) - require.NoError(t, err) + require.NoError(t, m.ensurePendingChannel(eth.BlockID{})) returnedTxData, err = m.nextTxData() require.ErrorIs(t, err, io.EOF) require.Equal(t, txData{}, returnedTxData) @@ -180,7 +172,6 @@ func TestChannelManagerNextTxData(t *testing.T) { func TestClearChannelManager(t *testing.T) { // Create a channel manager log := testlog.Logger(t, log.LvlCrit) - rng := rand.New(rand.NewSource(time.Now().UnixNano())) m := NewChannelManager(log, ChannelConfig{ // Need to set the channel timeout here so we don't clear pending // channels on confirmation. This would result in [TxConfirmed] @@ -188,7 +179,9 @@ func TestClearChannelManager(t *testing.T) { ChannelTimeout: 10, // Have to set the max frame size here otherwise the channel builder would not // be able to output any frames - MaxFrameSize: 24, + MaxFrameSize: 24, + TargetFrameSize: 24, + ApproxComprRatio: 1.0, }) // Channel Manager state should be empty by default @@ -199,18 +192,32 @@ func TestClearChannelManager(t *testing.T) { require.Empty(t, m.confirmedTransactions) // Add a block to the channel manager - a, _ := derivetest.RandomL2Block(rng, 4) + lBlock := types.NewBlock(&types.Header{ + BaseFee: big.NewInt(10), + Difficulty: common.Big0, + Number: big.NewInt(100), + }, nil, nil, nil, trie.NewStackTrie(nil)) + l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) + require.NoError(t, err) + txs := []*types.Transaction{types.NewTx(l1InfoTx)} + for i := 0; i < 100; i++ { + txData := make([]byte, 32) + _, _ = rand.Read(txData) + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), txData) + txs = append(txs, tx) + } + a := types.NewBlock(&types.Header{ + Number: big.NewInt(0), + }, txs, nil, nil, trie.NewStackTrie(nil)) newL1Tip := a.Hash() l1BlockID := eth.BlockID{ Hash: a.Hash(), Number: a.NumberU64(), } - err := m.AddL2Block(a) - require.NoError(t, err) + require.NoError(t, m.AddL2Block(a)) // Make sure there is a channel builder - err = m.ensurePendingChannel(l1BlockID) - require.NoError(t, err) + require.NoError(t, m.ensurePendingChannel(l1BlockID)) require.NotNil(t, m.pendingChannel) require.Equal(t, 0, len(m.confirmedTransactions)) @@ -218,10 +225,8 @@ func TestClearChannelManager(t *testing.T) { // We should have a pending channel with 1 frame // and no more blocks since processBlocks consumes // the list - err = m.processBlocks() - require.NoError(t, err) - err = m.pendingChannel.OutputFrames() - require.NoError(t, err) + require.NoError(t, m.processBlocks()) + require.NoError(t, m.pendingChannel.OutputFrames()) _, err = m.nextTxData() require.NoError(t, err) require.Equal(t, 0, len(m.blocks)) @@ -234,8 +239,7 @@ func TestClearChannelManager(t *testing.T) { Number: big.NewInt(1), ParentHash: a.Hash(), }, nil, nil, nil, nil) - err = m.AddL2Block(b) - require.NoError(t, err) + require.NoError(t, m.AddL2Block(b)) require.Equal(t, 1, len(m.blocks)) require.Equal(t, b.Hash(), m.tip) @@ -263,8 +267,7 @@ func TestChannelManagerTxConfirmed(t *testing.T) { // Let's add a valid pending transaction to the channel manager // So we can demonstrate that TxConfirmed's correctness - err := m.ensurePendingChannel(eth.BlockID{}) - require.NoError(t, err) + require.NoError(t, m.ensurePendingChannel(eth.BlockID{})) channelID := m.pendingChannel.ID() frame := frameData{ data: []byte{}, @@ -312,8 +315,7 @@ func TestChannelManagerTxFailed(t *testing.T) { // Let's add a valid pending transaction to the channel // manager so we can demonstrate correctness - err := m.ensurePendingChannel(eth.BlockID{}) - require.NoError(t, err) + require.NoError(t, m.ensurePendingChannel(eth.BlockID{})) channelID := m.pendingChannel.ID() frame := frameData{ data: []byte{}, @@ -359,8 +361,7 @@ func TestChannelManager_TxResend(t *testing.T) { a, _ := derivetest.RandomL2Block(rng, 4) - err := m.AddL2Block(a) - require.NoError(err) + require.NoError(m.AddL2Block(a)) txdata0, err := m.TxData(eth.BlockID{}) require.NoError(err) From d877b33311ede7aff63c01cc22e0169fcdcecce6 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Fri, 17 Mar 2023 13:02:03 -0700 Subject: [PATCH 061/404] contracts-bedrock: portal guardian config --- .../contracts/L1/OptimismPortal.sol | 2 +- .../deploy-config/devnetL1.json | 1 + .../deploy-config/final-migration-rehearsal.json | 1 + .../deploy-config/getting-started.json | 1 + .../deploy-config/goerli-forked.json | 1 + .../contracts-bedrock/deploy-config/goerli.json | 1 + .../contracts-bedrock/deploy-config/hardhat.json | 1 + .../deploy-config/internal-devnet.json | 1 + .../deploy/013-OptimismPortalImpl.ts | 16 +++++++--------- packages/contracts-bedrock/src/deploy-config.ts | 9 +++++++++ 10 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol index e195b5826..fe9521e00 100644 --- a/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol @@ -49,7 +49,7 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { L2OutputOracle public immutable L2_ORACLE; /** - * @notice Address that has the ability to pause and unpause deposits and withdrawals. + * @notice Address that has the ability to pause and unpause withdrawals. */ address public immutable GUARDIAN; diff --git a/packages/contracts-bedrock/deploy-config/devnetL1.json b/packages/contracts-bedrock/deploy-config/devnetL1.json index 4ed542993..41aaf071c 100644 --- a/packages/contracts-bedrock/deploy-config/devnetL1.json +++ b/packages/contracts-bedrock/deploy-config/devnetL1.json @@ -20,6 +20,7 @@ "sequencerFeeVaultRecipient": "0xfabb0ac9d68b0b445fb7357272ff202c5651694a", "proxyAdminOwner": "0xBcd4042DE499D14e55001CcbB24a551F3b954096", "finalSystemOwner": "0xBcd4042DE499D14e55001CcbB24a551F3b954096", + "portalGuardian": "0xBcd4042DE499D14e55001CcbB24a551F3b954096", "controller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "finalizationPeriodSeconds": 2, "deploymentWaitConfirmations": 1, diff --git a/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json b/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json index 3adf3d5c5..60d3eaa2a 100644 --- a/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json +++ b/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json @@ -1,5 +1,6 @@ { "finalSystemOwner": "0x62790eFcB3a5f3A5D398F95B47930A9Addd83807", + "portalGuardian": "0x62790eFcB3a5f3A5D398F95B47930A9Addd83807", "controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675", "l1StartingBlockTag": "0x4104895a540d87127ff11eef0d51d8f63ce00a6fc211db751a45a4b3a61a9c83", diff --git a/packages/contracts-bedrock/deploy-config/getting-started.json b/packages/contracts-bedrock/deploy-config/getting-started.json index ffe909d38..5bf01a9d1 100644 --- a/packages/contracts-bedrock/deploy-config/getting-started.json +++ b/packages/contracts-bedrock/deploy-config/getting-started.json @@ -2,6 +2,7 @@ "numDeployConfirmations": 1, "finalSystemOwner": "ADMIN", + "portalGuardian": "ADMIN", "controller": "ADMIN", "l1StartingBlockTag": "BLOCKHASH", diff --git a/packages/contracts-bedrock/deploy-config/goerli-forked.json b/packages/contracts-bedrock/deploy-config/goerli-forked.json index b6469198c..717efdc3f 100644 --- a/packages/contracts-bedrock/deploy-config/goerli-forked.json +++ b/packages/contracts-bedrock/deploy-config/goerli-forked.json @@ -1,5 +1,6 @@ { "finalSystemOwner": "0x62790eFcB3a5f3A5D398F95B47930A9Addd83807", + "portalGuardian": "0x62790eFcB3a5f3A5D398F95B47930A9Addd83807", "controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675", "l1StartingBlockTag": "0x4104895a540d87127ff11eef0d51d8f63ce00a6fc211db751a45a4b3a61a9c83", diff --git a/packages/contracts-bedrock/deploy-config/goerli.json b/packages/contracts-bedrock/deploy-config/goerli.json index 8457fabe4..6967827a2 100644 --- a/packages/contracts-bedrock/deploy-config/goerli.json +++ b/packages/contracts-bedrock/deploy-config/goerli.json @@ -2,6 +2,7 @@ "numDeployConfirmations": 1, "finalSystemOwner": "0xBc1233d0C3e6B5d53Ab455cF65A6623F6dCd7e4f", + "portalGuardian": "0xBc1233d0C3e6B5d53Ab455cF65A6623F6dCd7e4f", "controller": "0xBc1233d0C3e6B5d53Ab455cF65A6623F6dCd7e4f", "l1StartingBlockTag": "0x6ffc1bf3754c01f6bb9fe057c1578b87a8571ce2e9be5ca14bace6eccfd336c7", diff --git a/packages/contracts-bedrock/deploy-config/hardhat.json b/packages/contracts-bedrock/deploy-config/hardhat.json index 7e4aaabae..cb40d0bbb 100644 --- a/packages/contracts-bedrock/deploy-config/hardhat.json +++ b/packages/contracts-bedrock/deploy-config/hardhat.json @@ -1,5 +1,6 @@ { "finalSystemOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "portalGuardian": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "controller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "l1StartingBlockTag": "earliest", diff --git a/packages/contracts-bedrock/deploy-config/internal-devnet.json b/packages/contracts-bedrock/deploy-config/internal-devnet.json index e4f919d1b..a788e2d94 100644 --- a/packages/contracts-bedrock/deploy-config/internal-devnet.json +++ b/packages/contracts-bedrock/deploy-config/internal-devnet.json @@ -1,5 +1,6 @@ { "finalSystemOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF", + "finalSystemOwner": "portalGuardian", "controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675", "l1StartingBlockTag": "0x19c7e6b18fe156e45f4cfef707294fd8f079fa9c30a7b7cd6ec1ce3682ec6a2e", diff --git a/packages/contracts-bedrock/deploy/013-OptimismPortalImpl.ts b/packages/contracts-bedrock/deploy/013-OptimismPortalImpl.ts index c148694b9..870c6d553 100644 --- a/packages/contracts-bedrock/deploy/013-OptimismPortalImpl.ts +++ b/packages/contracts-bedrock/deploy/013-OptimismPortalImpl.ts @@ -17,13 +17,11 @@ const deployFn: DeployFunction = async (hre) => { 'L2OutputOracleProxy' ) - const finalSystemOwner = hre.deployConfig.finalSystemOwner - const finalSystemOwnerCode = await hre.ethers.provider.getCode( - finalSystemOwner - ) - if (finalSystemOwnerCode === '0x') { + const portalGuardian = hre.deployConfig.portalGuardian + const portalGuardianCode = await hre.ethers.provider.getCode(portalGuardian) + if (portalGuardianCode === '0x') { console.log( - `WARNING: setting OptimismPortal.GUARDIAN to ${finalSystemOwner} and it has no code` + `WARNING: setting OptimismPortal.GUARDIAN to ${portalGuardian} and it has no code` ) if (!isLiveDeployer) { throw new Error( @@ -35,13 +33,13 @@ const deployFn: DeployFunction = async (hre) => { // Deploy the OptimismPortal implementation as paused to // ensure that users do not interact with it and instead // interact with the proxied contract. - // The `finalSystemOwner` is set at the GUARDIAN. + // The `portalGuardian` is set at the GUARDIAN. await deploy({ hre, name: 'OptimismPortal', args: [ L2OutputOracleProxy.address, - finalSystemOwner, + portalGuardian, true, // paused ], postDeployAction: async (contract) => { @@ -53,7 +51,7 @@ const deployFn: DeployFunction = async (hre) => { await assertContractVariable( contract, 'GUARDIAN', - hre.deployConfig.finalSystemOwner + hre.deployConfig.portalGuardian ) }, }) diff --git a/packages/contracts-bedrock/src/deploy-config.ts b/packages/contracts-bedrock/src/deploy-config.ts index 3495cafe7..2cf2eb73c 100644 --- a/packages/contracts-bedrock/src/deploy-config.ts +++ b/packages/contracts-bedrock/src/deploy-config.ts @@ -14,6 +14,12 @@ interface RequiredDeployConfig { */ finalSystemOwner?: string + /** + * Address that is deployed as the GUARDIAN in the OptimismPortal. Has the + * ability to pause withdrawals. + */ + portalGuardian: string + /** * Address that will own the entire system on L1 during the deployment process. This address will * not own the system after the deployment is complete, ownership will be transferred to the @@ -181,6 +187,9 @@ export const deployConfigSpec: { finalSystemOwner: { type: 'address', }, + portalGuardian: { + type: 'address', + }, controller: { type: 'address', }, From 73e8e423e0328a2ab9718f35edddb3f4e06f7551 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Fri, 17 Mar 2023 13:05:27 -0700 Subject: [PATCH 062/404] op-chain-ops: portal guardian config support --- op-chain-ops/genesis/config.go | 5 +++++ op-chain-ops/genesis/layer_one.go | 2 +- op-chain-ops/genesis/testdata/test-deploy-config-full.json | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index f8b5c8454..71c152c66 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -76,6 +76,8 @@ type DeployConfig struct { ProxyAdminOwner common.Address `json:"proxyAdminOwner"` // Owner of the system on L1 FinalSystemOwner common.Address `json:"finalSystemOwner"` + // GUARDIAN account in the OptimismPortal + PortalGuardian common.Address `json:"portalGuardian"` // L1 recipient of fees accumulated in the BaseFeeVault BaseFeeVaultRecipient common.Address `json:"baseFeeVaultRecipient"` // L1 recipient of fees accumulated in the L1FeeVault @@ -128,6 +130,9 @@ func (d *DeployConfig) Check() error { if d.FinalizationPeriodSeconds == 0 { return fmt.Errorf("%w: FinalizationPeriodSeconds cannot be 0", ErrInvalidDeployConfig) } + if d.PortalGuardian == (common.Address{}) { + return fmt.Errorf("%w: PortalGuardian cannot be address(0)", ErrInvalidDeployConfig) + } if d.MaxSequencerDrift == 0 { return fmt.Errorf("%w: MaxSequencerDrift cannot be 0", ErrInvalidDeployConfig) } diff --git a/op-chain-ops/genesis/layer_one.go b/op-chain-ops/genesis/layer_one.go index 317650c67..dd30f3556 100644 --- a/op-chain-ops/genesis/layer_one.go +++ b/op-chain-ops/genesis/layer_one.go @@ -295,7 +295,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) Name: "OptimismPortal", Args: []interface{}{ predeploys.DevL2OutputOracleAddr, - config.FinalSystemOwner, + config.PortalGuardian, true, // _paused }, }, diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index 6d71ea6b0..af04f2e31 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -19,6 +19,7 @@ "l1GenesisBlockGasLimit": "0xe4e1c0", "l1GenesisBlockDifficulty": "0x1", "finalSystemOwner": "0x0000000000000000000000000000000000000111", + "portalGuardian": "0x0000000000000000000000000000000000000112", "finalizationPeriodSeconds": 2, "l1GenesisBlockMixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "l1GenesisBlockCoinbase": "0x0000000000000000000000000000000000000000", From d7658477517411fdb3a3af97b63edb01e5d745ea Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Fri, 17 Mar 2023 13:09:18 -0700 Subject: [PATCH 063/404] fixup --- packages/contracts-bedrock/deploy-config/internal-devnet.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/deploy-config/internal-devnet.json b/packages/contracts-bedrock/deploy-config/internal-devnet.json index a788e2d94..e352402ca 100644 --- a/packages/contracts-bedrock/deploy-config/internal-devnet.json +++ b/packages/contracts-bedrock/deploy-config/internal-devnet.json @@ -1,6 +1,6 @@ { "finalSystemOwner": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF", - "finalSystemOwner": "portalGuardian", + "portalGuardian": "0x858F0751ef8B4067f0d2668C076BDB50a8549fbF", "controller": "0x2d30335B0b807bBa1682C487BaAFD2Ad6da5D675", "l1StartingBlockTag": "0x19c7e6b18fe156e45f4cfef707294fd8f079fa9c30a7b7cd6ec1ce3682ec6a2e", From c5d0cfb4995320dd51b06f67a79bb9d45708a7b7 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Fri, 17 Mar 2023 15:22:31 -0500 Subject: [PATCH 064/404] feat(getting-started): Change the ETH to go to L1StandardBridge Same effect, but the resulting tx is traceable --- docs/op-stack/src/docs/build/getting-started.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/op-stack/src/docs/build/getting-started.md b/docs/op-stack/src/docs/build/getting-started.md index 24dd8fff0..7d869d038 100644 --- a/docs/op-stack/src/docs/build/getting-started.md +++ b/docs/op-stack/src/docs/build/getting-started.md @@ -440,20 +440,27 @@ Once you’ve connected your wallet, you’ll probably notice that you don’t h cd ~/optimism/packages/contracts-bedrock ``` -1. Grab the address of the `OptimismPortalProxy` contract: +1. Grab the address of the proxy to the L1 standard bridge contract: ```bash - cat deployments/getting-started/OptimismPortalProxy.json | grep \"address\": + cat deployments/getting-started/Proxy__OVM_L1StandardBridge.json.json | grep \"address\": ``` You should see a result like the following (**your address will be different**): ``` - "address": "0x264B5fde6B37fb6f1C92AaC17BA144cf9e3DcFE9", - "address": "0x264B5fde6B37fb6f1C92AaC17BA144cf9e3DcFE9", + "address": "0x874f2E16D803c044F10314A978322da3c9b075c7", + "internalType": "address", + "type": "address" + "internalType": "address", + "type": "address" + "internalType": "address", + "type": "address" + "internalType": "address", + "type": "address" ``` -1. Grab the `OptimismPortalProxy` address and, using the wallet that you want to have ETH on your Rollup, send that address a small amount of ETH on Goerli (0.1 or less is fine). It may take up to 5 minutes for that ETH to appear in your wallet on L2. +1. Grab the L1 bridge proxy contract address and, using the wallet that you want to have ETH on your Rollup, send that address a small amount of ETH on Goerli (0.1 or less is fine). It may take up to 5 minutes for that ETH to appear in your wallet on L2. ## Use your Rollup From c0ebd1a47893eae38cdb41a5ab0d33982e75b568 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 17:24:51 -0400 Subject: [PATCH 065/404] stub out txmgr tx creation --- op-batcher/batcher/config.go | 1 + op-batcher/batcher/driver.go | 45 +++++++++++++--- op-service/txmgr/txmgr.go | 93 ++++++++++++++++++++++++++++++++++ op-service/txmgr/txmgr_test.go | 7 +++ 4 files changed, 139 insertions(+), 7 deletions(-) diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index 7c0b9f9b9..08c4c3172 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -27,6 +27,7 @@ type Config struct { PollInterval time.Duration TxManagerConfig txmgr.Config From common.Address + NetworkTimeout time.Duration // RollupConfig is queried at startup Rollup *rollup.Config diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index e288f60fe..3e67e54ec 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -22,7 +22,7 @@ import ( type BatchSubmitter struct { Config // directly embed the config + sources - txMgr *TransactionManager + txMgr txmgr.TxManager wg sync.WaitGroup done chan struct{} @@ -120,9 +120,10 @@ func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger) (*BatchSub return &BatchSubmitter{ Config: cfg, - txMgr: NewTransactionManager(l, - cfg.TxManagerConfig, cfg.Rollup.BatchInboxAddress, cfg.Rollup.L1ChainID, - cfg.From, cfg.L1Client), + txMgr: txmgr.NewSimpleTxManager("batcher", l, cfg.TxManagerConfig, cfg.L1Client), + // NewTransactionManager(l, + // cfg.TxManagerConfig, cfg.Rollup.BatchInboxAddress, cfg.Rollup.L1ChainID, + // cfg.From, cfg.L1Client), state: NewChannelManager(l, cfg.Channel), }, nil @@ -209,7 +210,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) { // loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded. func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (eth.BlockID, error) { - ctx, cancel := context.WithTimeout(ctx, networkTimeout) + ctx, cancel := context.WithTimeout(ctx, l.NetworkTimeout) block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) cancel() if err != nil { @@ -226,7 +227,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin // calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state. // It also takes care of initializing some local state (i.e. will modify l.lastStoredBlock in certain conditions) func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) { - childCtx, cancel := context.WithTimeout(ctx, networkTimeout) + childCtx, cancel := context.WithTimeout(ctx, l.NetworkTimeout) defer cancel() syncStatus, err := l.RollupNode.SyncStatus(childCtx) // Ensure that we have the sync status @@ -294,7 +295,7 @@ func (l *BatchSubmitter) loop() { break } // Record TX Status - if receipt, err := l.txMgr.SendTransaction(l.ctx, txdata.Bytes()); err != nil { + if receipt, err := l.SendTransaction(l.ctx, txdata.Bytes()); err != nil { l.recordFailedTx(txdata.ID(), err) } else { l.recordConfirmedTx(txdata.ID(), receipt) @@ -316,6 +317,36 @@ func (l *BatchSubmitter) loop() { } } +// SendTransaction creates & submits a transaction to the batch inbox address with the given `data`. +// It currently uses the underlying `txmgr` to handle transaction sending & price management. +// This is a blocking method. It should not be called concurrently. +// TODO: where to put concurrent transaction handling logic. +func (l *BatchSubmitter) SendTransaction(ctx context.Context, data []byte) (*types.Receipt, error) { + tx, err := l.txMgr.CraftTx(ctx, txmgr.TxCandidate{ + Recipient: l.Rollup.BatchInboxAddress, + TxData: data, + From: l.From, + ChainID: l.Rollup.L1ChainID, + // Explicit instantiation here so we can make a note that a gas + // limit of 0 will cause the [txmgr] to estimate the gas limit. + GasLimit: 0, + }) + if err != nil { + return nil, fmt.Errorf("failed to create tx: %w", err) + } + + // TODO: Select a timeout that makes sense here. + ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) + defer cancel() + if receipt, err := l.txMgr.Send(ctx, tx); err != nil { + l.log.Warn("unable to publish tx", "err", err, "data_size", len(data)) + return nil, err + } else { + l.log.Info("tx successfully published", "tx_hash", receipt.TxHash, "data_size", len(data)) + return receipt, nil + } +} + func (l *BatchSubmitter) recordFailedTx(id txID, err error) { l.log.Warn("Failed to send transaction", "err", err) l.state.TxFailed(id) diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 754b69aa3..8e686cabe 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -3,14 +3,17 @@ package txmgr import ( "context" "errors" + "fmt" "math/big" "sync" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" ) @@ -38,6 +41,12 @@ type Config struct { // attempted. ResubmissionTimeout time.Duration + // NetworkTimeout is the allowed duration for a single network request. + // This is intended to be used for network requests that can be replayed. + // + // If not set, this will default to 2 seconds. + NetworkTimeout time.Duration + // RequireQueryInterval is the interval at which the tx manager will // query the backend to check for confirmations after a tx at a // specific gas price has been published. @@ -69,6 +78,9 @@ type TxManager interface { // // NOTE: Send should be called by AT MOST one caller at a time. Send(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) + + // CraftTx is used to craft a transaction using a [TxCandidate]. + CraftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) } // ETHBackend is the set of methods that the transaction manager uses to resubmit gas & determine @@ -89,6 +101,7 @@ type ETHBackend interface { // TODO(CLI-3318): Maybe need a generic interface to support different RPC providers HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error) + NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) } // SimpleTxManager is a implementation of TxManager that performs linear fee @@ -101,6 +114,86 @@ type SimpleTxManager struct { l log.Logger } +// TxCandidate is a transaction candidate that can be submitted to ask the +// [TxManager] to construct a transaction with gas price bounds. +type TxCandidate struct { + // TxData is the transaction data to be used in the constructed tx. + TxData []byte + // Recipient is the recipient (or `to`) of the constructed tx. + Recipient common.Address + // GasLimit is the gas limit to be used in the constructed tx. + GasLimit uint64 + // From is the sender (or `from`) of the constructed tx. + From common.Address + /// ChainID is the chain ID to be used in the constructed tx. + ChainID *big.Int +} + +// calcGasTipAndFeeCap queries L1 to determine what a suitable miner tip & basefee limit would be for timely inclusion +func (m *SimpleTxManager) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap *big.Int, gasFeeCap *big.Int, err error) { + childCtx, cancel := context.WithTimeout(ctx, m.Config.NetworkTimeout) + gasTipCap, err = m.backend.SuggestGasTipCap(childCtx) + cancel() + if err != nil { + return nil, nil, fmt.Errorf("failed to get suggested gas tip cap: %w", err) + } + + if gasTipCap == nil { + m.l.Warn("unexpected unset gasTipCap, using default 2 gwei") + gasTipCap = new(big.Int).SetUint64(params.GWei * 2) + } + + childCtx, cancel = context.WithTimeout(ctx, m.Config.NetworkTimeout) + head, err := m.backend.HeaderByNumber(childCtx, nil) + cancel() + if err != nil || head == nil { + return nil, nil, fmt.Errorf("failed to get L1 head block for fee cap: %w", err) + } + if head.BaseFee == nil { + return nil, nil, fmt.Errorf("failed to get L1 basefee in block %d for fee cap", head.Number) + } + gasFeeCap = CalcGasFeeCap(head.BaseFee, gasTipCap) + + return gasTipCap, gasFeeCap, nil +} + +// CraftTx creates the signed transaction to the batchInboxAddress. +// It queries L1 for the current fee market conditions as well as for the nonce. +// NOTE: This method SHOULD NOT publish the resulting transaction. +func (m *SimpleTxManager) CraftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) { + gasTipCap, gasFeeCap, err := m.calcGasTipAndFeeCap(ctx) + if err != nil { + return nil, err + } + + childCtx, cancel := context.WithTimeout(ctx, m.Config.NetworkTimeout) + nonce, err := m.backend.NonceAt(childCtx, candidate.From, nil) + cancel() + if err != nil { + return nil, fmt.Errorf("failed to get nonce: %w", err) + } + + rawTx := &types.DynamicFeeTx{ + ChainID: candidate.ChainID, + Nonce: nonce, + To: &candidate.Recipient, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Data: candidate.TxData, + } + m.l.Info("creating tx", "to", rawTx.To, "from", candidate.From) + + gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + if err != nil { + return nil, fmt.Errorf("failed to calculate intrinsic gas: %w", err) + } + rawTx.Gas = gas + + ctx, cancel = context.WithTimeout(ctx, m.Config.NetworkTimeout) + defer cancel() + return m.Signer(ctx, candidate.From, types.NewTx(rawTx)) +} + // IncreaseGasPrice takes the previous transaction & potentially clones then signs it with a higher tip. // If the tip + basefee suggested by the network are not greater than the previous values, the same transaction // will be returned. If they are greater, this function will ensure that they are at least greater by 15% than diff --git a/op-service/txmgr/txmgr_test.go b/op-service/txmgr/txmgr_test.go index 4d60e2455..71810bd5e 100644 --- a/op-service/txmgr/txmgr_test.go +++ b/op-service/txmgr/txmgr_test.go @@ -185,7 +185,10 @@ func (b *mockBackend) SendTransaction(ctx context.Context, tx *types.Transaction panic("set sender function was not set") } return b.send(ctx, tx) +} +func (b *mockBackend) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { + return 0, nil } // TransactionReceipt queries the mockBackend for a mined txHash. If none is @@ -577,6 +580,10 @@ func (b *failingBackend) SuggestGasTipCap(_ context.Context) (*big.Int, error) { return b.gasTip, nil } +func (b *failingBackend) NonceAt(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) { + return 0, errors.New("unimplemented") +} + // TestWaitMinedReturnsReceiptAfterFailure asserts that WaitMined is able to // recover from failed calls to the backend. It uses the failedBackend to // simulate an rpc call failure, followed by the successful return of a receipt. From 472cc5608b56b54d79b1576b1e9becb1f3e9b89d Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 17:31:52 -0400 Subject: [PATCH 066/404] nit fixes :sparkles: --- op-node/rollup/derive/channel_out_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/op-node/rollup/derive/channel_out_test.go b/op-node/rollup/derive/channel_out_test.go index 44fa6ee40..b23a26623 100644 --- a/op-node/rollup/derive/channel_out_test.go +++ b/op-node/rollup/derive/channel_out_test.go @@ -36,14 +36,10 @@ func TestOutputFrameSmallMaxSize(t *testing.T) { cout, err := NewChannelOut() require.NoError(t, err) - // Set the channel out frame details - cout.id = ChannelID{0x01} - cout.frame = 0 - // Call OutputFrame with the range of small max size values that err - w := bytes.NewBuffer([]byte{}) + var w bytes.Buffer for i := 0; i < 23; i++ { - fid, err := cout.OutputFrame(w, uint64(i)) + fid, err := cout.OutputFrame(&w, uint64(i)) require.ErrorIs(t, err, ErrMaxFrameSizeTooSmall) require.Zero(t, fid) } From d0adf42edc84c9f6b438031721be0c34f99859cd Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Fri, 17 Mar 2023 14:59:06 -0700 Subject: [PATCH 067/404] op-service/txmgr: Better handle price bumps This doesn't fully solve the problem that we need to bump both the FeeCap and FeeTip by 10% for geth to accept it, but it handles it slightly better than it did. --- op-service/txmgr/txmgr.go | 19 +++++++-- op-service/txmgr/txmgr_test.go | 78 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 754b69aa3..a09a5d9d1 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -154,11 +154,22 @@ func (m *SimpleTxManager) IncreaseGasPrice(ctx context.Context, tx *types.Transa thresholdFeeCap := new(big.Int).Mul(priceBumpPercent, tx.GasFeeCap()) thresholdFeeCap = thresholdFeeCap.Div(thresholdFeeCap, oneHundred) if tx.GasFeeCapIntCmp(gasFeeCap) >= 0 { - m.l.Debug("Reusing the previous fee cap", "previous", tx.GasFeeCap(), "suggested", gasFeeCap) - gasFeeCap = tx.GasFeeCap() - reusedFeeCap = true + if reusedTip { + m.l.Debug("Reusing the previous fee cap", "previous", tx.GasFeeCap(), "suggested", gasFeeCap) + gasFeeCap = tx.GasFeeCap() + reusedFeeCap = true + } else { + m.l.Debug("Overriding the fee cap to enforce a price bump because we increased the tip", "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap) + gasFeeCap = thresholdFeeCap + } } else if thresholdFeeCap.Cmp(gasFeeCap) > 0 { - m.l.Debug("Overriding the fee cap to enforce a price bump", "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap) + if reusedTip { + // TODO (CLI-3620): Increase the basefee then recompute the feecap + m.l.Warn("Overriding the fee cap to enforce a price bump without increasing the tip. Will likely result in ErrReplacementUnderpriced", + "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap) + } else { + m.l.Debug("Overriding the fee cap to enforce a price bump", "previous", tx.GasFeeCap(), "suggested", gasFeeCap, "new", thresholdFeeCap) + } gasFeeCap = thresholdFeeCap } diff --git a/op-service/txmgr/txmgr_test.go b/op-service/txmgr/txmgr_test.go index 4d60e2455..d107a13e8 100644 --- a/op-service/txmgr/txmgr_test.go +++ b/op-service/txmgr/txmgr_test.go @@ -621,6 +621,84 @@ func TestIncreaseGasPriceEnforcesMinBump(t *testing.T) { baseFee: big.NewInt(460), } + mgr := &SimpleTxManager{ + Config: Config{ + ResubmissionTimeout: time.Second, + ReceiptQueryInterval: 50 * time.Millisecond, + NumConfirmations: 1, + SafeAbortNonceTooLowCount: 3, + Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { + return tx, nil + }, + From: common.Address{}, + }, + name: "TEST", + backend: &borkedBackend, + l: testlog.Logger(t, log.LvlTrace), + } + + tx := types.NewTx(&types.DynamicFeeTx{ + GasTipCap: big.NewInt(100), + GasFeeCap: big.NewInt(1000), + }) + + ctx := context.Background() + newTx, err := mgr.IncreaseGasPrice(ctx, tx) + require.NoError(t, err) + require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger") + require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger") +} + +// TestIncreaseGasPriceEnforcesMinBumpForBothOnTipIncrease asserts that if the gasTip goes up, +// but the baseFee doesn't, both values are increased by 10% +func TestIncreaseGasPriceEnforcesMinBumpForBothOnTipIncrease(t *testing.T) { + t.Parallel() + + borkedBackend := failingBackend{ + gasTip: big.NewInt(101), + baseFee: big.NewInt(440), + } + + mgr := &SimpleTxManager{ + Config: Config{ + ResubmissionTimeout: time.Second, + ReceiptQueryInterval: 50 * time.Millisecond, + NumConfirmations: 1, + SafeAbortNonceTooLowCount: 3, + Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { + return tx, nil + }, + From: common.Address{}, + }, + name: "TEST", + backend: &borkedBackend, + l: testlog.Logger(t, log.LvlCrit), + } + + tx := types.NewTx(&types.DynamicFeeTx{ + GasTipCap: big.NewInt(100), + GasFeeCap: big.NewInt(1000), + }) + + ctx := context.Background() + newTx, err := mgr.IncreaseGasPrice(ctx, tx) + require.NoError(t, err) + require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger") + require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger") +} + +// TestIncreaseGasPriceEnforcesMinBumpForBothOnBaseFeeIncrease asserts that if the baseFee goes up, +// but the tip doesn't, both values are increased by 10% +// TODO(CLI-3620): This test will fail until we implemented CLI-3620. +func TestIncreaseGasPriceEnforcesMinBumpForBothOnBaseFeeIncrease(t *testing.T) { + t.Skip("Failing until CLI-3620 is implemented") + t.Parallel() + + borkedBackend := failingBackend{ + gasTip: big.NewInt(99), + baseFee: big.NewInt(460), + } + mgr := &SimpleTxManager{ Config: Config{ ResubmissionTimeout: time.Second, From b16067a9f31fc0d10b5e9ff62071358f1434dc21 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Fri, 17 Mar 2023 18:24:01 -0400 Subject: [PATCH 068/404] feat(ctb): reduce the timeout on awaitCondition 30 seconds is too long, it feels like the script is broken. --- .changeset/clever-jokes-notice.md | 5 +++++ .../contracts-bedrock/deploy/020-SystemDictatorSteps-1.ts | 6 +++--- .../contracts-bedrock/deploy/021-SystemDictatorSteps-2.ts | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/clever-jokes-notice.md diff --git a/.changeset/clever-jokes-notice.md b/.changeset/clever-jokes-notice.md new file mode 100644 index 000000000..c5d0e63fa --- /dev/null +++ b/.changeset/clever-jokes-notice.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/contracts-bedrock': patch +--- + +Reduce the time that the system dictator deploy scripts wait before checking the chain state. diff --git a/packages/contracts-bedrock/deploy/020-SystemDictatorSteps-1.ts b/packages/contracts-bedrock/deploy/020-SystemDictatorSteps-1.ts index 84d1a2a15..80a55d34f 100644 --- a/packages/contracts-bedrock/deploy/020-SystemDictatorSteps-1.ts +++ b/packages/contracts-bedrock/deploy/020-SystemDictatorSteps-1.ts @@ -109,7 +109,7 @@ const deployFn: DeployFunction = async (hre) => { const owner = await AddressManager.owner() return owner === SystemDictator.address }, - 30000, + 5000, 1000 ) } else { @@ -149,7 +149,7 @@ const deployFn: DeployFunction = async (hre) => { }) return owner === SystemDictator.address }, - 30000, + 5000, 1000 ) } else { @@ -187,7 +187,7 @@ const deployFn: DeployFunction = async (hre) => { }) return owner === SystemDictator.address }, - 30000, + 5000, 1000 ) } else { diff --git a/packages/contracts-bedrock/deploy/021-SystemDictatorSteps-2.ts b/packages/contracts-bedrock/deploy/021-SystemDictatorSteps-2.ts index 1f9268721..906bd4253 100644 --- a/packages/contracts-bedrock/deploy/021-SystemDictatorSteps-2.ts +++ b/packages/contracts-bedrock/deploy/021-SystemDictatorSteps-2.ts @@ -203,7 +203,7 @@ const deployFn: DeployFunction = async (hre) => { async () => { return SystemDictator.dynamicConfigSet() }, - 30000, + 5000, 1000 ) } @@ -316,7 +316,7 @@ const deployFn: DeployFunction = async (hre) => { const paused = await OptimismPortal.paused() return !paused }, - 30000, + 5000, 1000 ) @@ -345,7 +345,7 @@ const deployFn: DeployFunction = async (hre) => { async () => { return SystemDictator.finalized() }, - 30000, + 5000, 1000 ) From 929077b1b405424bffb464f6313f38bba8849ad9 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 19:26:23 -0400 Subject: [PATCH 069/404] stash txmgr changes --- op-service/txmgr/txmgr.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 8e686cabe..3fd5524c5 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -101,6 +101,8 @@ type ETHBackend interface { // TODO(CLI-3318): Maybe need a generic interface to support different RPC providers HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error) + // NonceAt returns the account nonce of the given account. + // The block number can be nil, in which case the nonce is taken from the latest known block. NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) } @@ -166,6 +168,7 @@ func (m *SimpleTxManager) CraftTx(ctx context.Context, candidate TxCandidate) (* return nil, err } + // Fetch the sender's nonce from the latest known block (nil `blockNumber`) childCtx, cancel := context.WithTimeout(ctx, m.Config.NetworkTimeout) nonce, err := m.backend.NonceAt(childCtx, candidate.From, nil) cancel() @@ -181,8 +184,9 @@ func (m *SimpleTxManager) CraftTx(ctx context.Context, candidate TxCandidate) (* GasFeeCap: gasFeeCap, Data: candidate.TxData, } - m.l.Info("creating tx", "to", rawTx.To, "from", candidate.From) + // Calculate the intrinsic gas for the transaction + m.l.Info("creating tx", "to", rawTx.To, "from", candidate.From) gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) if err != nil { return nil, fmt.Errorf("failed to calculate intrinsic gas: %w", err) @@ -278,6 +282,9 @@ func NewSimpleTxManager(name string, l log.Logger, cfg Config, backend ETHBacken if cfg.NumConfirmations == 0 { panic("txmgr: NumConfirmations cannot be zero") } + if cfg.NetworkTimeout == 0 { + cfg.NetworkTimeout = 2 * time.Second + } return &SimpleTxManager{ name: name, From 334a6a88fef1513c8de8f8772ef9c257dbadaf1a Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 19:30:18 -0400 Subject: [PATCH 070/404] move fixed overhead into a const :gear: --- op-node/rollup/derive/channel_out.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index a0e9c688d..e80103070 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -18,6 +18,11 @@ var ErrMaxFrameSizeTooSmall = errors.New("maxSize is too small to fit the fixed var ErrNotDepositTx = errors.New("first transaction in block is not a deposit tx") var ErrTooManyRLPBytes = errors.New("batch would cause RLP bytes to go over limit") +// FrameV0OverHeadSize is the absolute minimum size of a frame. +// This is the fixed overhead frame size, +// calculated as follows: 16 + 2 + 4 + 1 = 23 bytes. +const FrameV0OverHeadSize = 23 + type ChannelOut struct { id ChannelID // Frame ID of the next frame to emit. Increment after emitting @@ -151,16 +156,13 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, erro FrameNumber: uint16(co.frame), } - // Error if the `maxSize` is too small to fit the fixed frame - // overhead as specified below. - if maxSize < 23 { + // Error if the `maxSize` is smaller than the [FrameV0OverHeadSize]. + if maxSize < FrameV0OverHeadSize { return 0, ErrMaxFrameSizeTooSmall } // Copy data from the local buffer into the frame data buffer - // Don't go past the maxSize with the fixed frame overhead. - // Fixed overhead: 16 + 2 + 4 + 1 = 23 bytes. - maxDataSize := maxSize - 23 + maxDataSize := maxSize - FrameV0OverHeadSize if maxDataSize > uint64(co.buf.Len()) { maxDataSize = uint64(co.buf.Len()) // If we are closed & will not spill past the current frame From 49e4b2c776d51e0da493c7b892e3c8ab908e0ece Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 19:33:36 -0400 Subject: [PATCH 071/404] move comment to godoc :memo: --- op-node/rollup/derive/channel_out.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index e80103070..cfb1db3e5 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -147,16 +147,17 @@ func (co *ChannelOut) Close() error { // OutputFrame writes a frame to w with a given max size and returns the frame // number. // Use `ReadyBytes`, `Flush`, and `Close` to modify the ready buffer. -// Returns io.EOF when the channel is closed & there are no more frames +// Returns an error if the `maxSize` < FrameV0OverHeadSize. +// Returns io.EOF when the channel is closed & there are no more frames. // Returns nil if there is still more buffered data. -// Returns and error if it ran into an error during processing. +// Returns anderror if it ran into an error during processing. func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error) { f := Frame{ ID: co.id, FrameNumber: uint16(co.frame), } - // Error if the `maxSize` is smaller than the [FrameV0OverHeadSize]. + // Check that the maxSize is large enough for the frame overhead size. if maxSize < FrameV0OverHeadSize { return 0, ErrMaxFrameSizeTooSmall } From 04dff94918c4c3471e09f75d0bacf6765ce36d63 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 19:40:58 -0400 Subject: [PATCH 072/404] revert random block gen :recycle: --- op-batcher/batcher/channel_manager_test.go | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 2bbf07100..17b3d9205 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -172,6 +172,7 @@ func TestChannelManagerNextTxData(t *testing.T) { func TestClearChannelManager(t *testing.T) { // Create a channel manager log := testlog.Logger(t, log.LvlCrit) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) m := NewChannelManager(log, ChannelConfig{ // Need to set the channel timeout here so we don't clear pending // channels on confirmation. This would result in [TxConfirmed] @@ -192,23 +193,7 @@ func TestClearChannelManager(t *testing.T) { require.Empty(t, m.confirmedTransactions) // Add a block to the channel manager - lBlock := types.NewBlock(&types.Header{ - BaseFee: big.NewInt(10), - Difficulty: common.Big0, - Number: big.NewInt(100), - }, nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) - require.NoError(t, err) - txs := []*types.Transaction{types.NewTx(l1InfoTx)} - for i := 0; i < 100; i++ { - txData := make([]byte, 32) - _, _ = rand.Read(txData) - tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), txData) - txs = append(txs, tx) - } - a := types.NewBlock(&types.Header{ - Number: big.NewInt(0), - }, txs, nil, nil, trie.NewStackTrie(nil)) + a, _ := derivetest.RandomL2Block(rng, 4) newL1Tip := a.Hash() l1BlockID := eth.BlockID{ Hash: a.Hash(), @@ -226,8 +211,9 @@ func TestClearChannelManager(t *testing.T) { // and no more blocks since processBlocks consumes // the list require.NoError(t, m.processBlocks()) + require.NoError(t, m.pendingChannel.co.Flush()) require.NoError(t, m.pendingChannel.OutputFrames()) - _, err = m.nextTxData() + _, err := m.nextTxData() require.NoError(t, err) require.Equal(t, 0, len(m.blocks)) require.Equal(t, newL1Tip, m.tip) From 7f42a06f61ab437e7c61ccaf830990c0f5e42efa Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Fri, 17 Mar 2023 20:05:08 -0400 Subject: [PATCH 073/404] minimize changeset :sparkles: --- op-node/rollup/derive/channel_out.go | 2 +- op-node/rollup/derive/channel_out_test.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index cfb1db3e5..60ceef58c 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -150,7 +150,7 @@ func (co *ChannelOut) Close() error { // Returns an error if the `maxSize` < FrameV0OverHeadSize. // Returns io.EOF when the channel is closed & there are no more frames. // Returns nil if there is still more buffered data. -// Returns anderror if it ran into an error during processing. +// Returns an error if it ran into an error during processing. func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error) { f := Frame{ ID: co.id, diff --git a/op-node/rollup/derive/channel_out_test.go b/op-node/rollup/derive/channel_out_test.go index b23a26623..09d6f1542 100644 --- a/op-node/rollup/derive/channel_out_test.go +++ b/op-node/rollup/derive/channel_out_test.go @@ -80,42 +80,42 @@ func TestForceCloseTxData(t *testing.T) { output: "", }, { - frames: []Frame{{FrameNumber: 0, IsLast: false}, {ID: id, FrameNumber: 1, IsLast: true}}, + frames: []Frame{Frame{FrameNumber: 0, IsLast: false}, Frame{ID: id, FrameNumber: 1, IsLast: true}}, errors: true, output: "", }, { - frames: []Frame{{ID: id, FrameNumber: 0, IsLast: false}}, + frames: []Frame{Frame{ID: id, FrameNumber: 0, IsLast: false}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000001", }, { - frames: []Frame{{ID: id, FrameNumber: 0, IsLast: true}}, + frames: []Frame{Frame{ID: id, FrameNumber: 0, IsLast: true}}, errors: false, output: "00", }, { - frames: []Frame{{ID: id, FrameNumber: 1, IsLast: false}}, + frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000001", }, { - frames: []Frame{{ID: id, FrameNumber: 1, IsLast: true}}, + frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000", }, { - frames: []Frame{{ID: id, FrameNumber: 2, IsLast: true}}, + frames: []Frame{Frame{ID: id, FrameNumber: 2, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00010000000000", }, { - frames: []Frame{{ID: id, FrameNumber: 1, IsLast: false}, {ID: id, FrameNumber: 3, IsLast: true}}, + frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}, Frame{ID: id, FrameNumber: 3, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000", }, { - frames: []Frame{{ID: id, FrameNumber: 1, IsLast: false}, {ID: id, FrameNumber: 3, IsLast: true}, {ID: id, FrameNumber: 5, IsLast: true}}, + frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}, Frame{ID: id, FrameNumber: 3, IsLast: true}, Frame{ID: id, FrameNumber: 5, IsLast: true}}, errors: false, output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000", }, From 4a0617ac3b47678dd3988cc8c779546f6bd0095e Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Fri, 17 Mar 2023 17:05:12 -0700 Subject: [PATCH 074/404] config: set the L2 gaslimit to 30 million in config Ensures that things are smooth once https://github.com/ethereum-optimism/optimism/pull/5112 is merged. A too small of gas limit will break things after that PR is merged. We will run with 30 million limit on mainnet, so we should ensure that our devnet config reflects that. --- packages/contracts-bedrock/deploy-config/devnetL1.json | 2 +- .../deploy-config/final-migration-rehearsal.json | 2 +- packages/contracts-bedrock/deploy-config/getting-started.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/deploy-config/devnetL1.json b/packages/contracts-bedrock/deploy-config/devnetL1.json index 41aaf071c..dd6c426e6 100644 --- a/packages/contracts-bedrock/deploy-config/devnetL1.json +++ b/packages/contracts-bedrock/deploy-config/devnetL1.json @@ -12,7 +12,7 @@ "l2OutputOracleStartingTimestamp": -1, "l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "l2OutputOracleChallenger": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", - "l2GenesisBlockGasLimit": "0xE4E1C0", + "l2GenesisBlockGasLimit": "0x1c9c380", "l1BlockTime": 3, "cliqueSignerAddress": "0xca062b0fd91172d89bcd4bb084ac4e21972cc467", "baseFeeVaultRecipient": "0xBcd4042DE499D14e55001CcbB24a551F3b954096", diff --git a/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json b/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json index 60d3eaa2a..3cab8bf05 100644 --- a/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json +++ b/packages/contracts-bedrock/deploy-config/final-migration-rehearsal.json @@ -28,7 +28,7 @@ "governanceTokenSymbol": "OP", "governanceTokenOwner": "0x038a8825A3C3B0c08d52Cc76E5E361953Cf6Dc76", - "l2GenesisBlockGasLimit": "0x17D7840", + "l2GenesisBlockGasLimit": "0x1c9c380", "l2GenesisBlockCoinbase": "0x4200000000000000000000000000000000000011", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", diff --git a/packages/contracts-bedrock/deploy-config/getting-started.json b/packages/contracts-bedrock/deploy-config/getting-started.json index 5bf01a9d1..5050b96da 100644 --- a/packages/contracts-bedrock/deploy-config/getting-started.json +++ b/packages/contracts-bedrock/deploy-config/getting-started.json @@ -40,7 +40,7 @@ "governanceTokenName": "Optimism", "governanceTokenOwner": "ADMIN", - "l2GenesisBlockGasLimit": "0x17D7840", + "l2GenesisBlockGasLimit": "0x1c9c380", "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", "l2GenesisRegolithTimeOffset": "0x0", From b34680a9e35ff7854d3ef4d1a1ae5db462b5f192 Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Fri, 17 Mar 2023 18:09:20 -0700 Subject: [PATCH 075/404] op-proposer: log L2OutputOracle version + address Logs the L2OutputOracle version and address after building a client to call it. This is generally useful information to have and future implementations of the `op-proposer` will need to function differently depending on the version to keep the software compatible with both permissioned and permissionless output proposal submission. --- op-proposer/proposer/l2_output_submitter.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index c4e946e96..72bc25f5a 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -208,6 +208,13 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) return nil, err } + version, err := l2ooContract.Version(&bind.CallOpts{}) + if err != nil { + cancel() + return nil, err + } + log.Info("Connected to L2OutputOracle", "address", cfg.L2OutputOracleAddr, "version", version) + parsed, err := abi.JSON(strings.NewReader(bindings.L2OutputOracleMetaData.ABI)) if err != nil { cancel() From 938707eae9f873c42cb48f1dd6ce3a0e2e14b4dc Mon Sep 17 00:00:00 2001 From: clabby Date: Sun, 19 Mar 2023 14:01:06 -0400 Subject: [PATCH 076/404] feat(ctb): Add test for `finalizeBridgeERC721` reverting if the token is not compliant with the `IOptimismMintableERC721` interface --- packages/contracts-bedrock/.gas-snapshot | 1 + .../contracts/test/L2ERC721Bridge.t.sol | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index 096e9fa53..89013ebdd 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -141,6 +141,7 @@ L2ERC721Bridge_Test:test_bridgeERC721_succeeds() (gas: 144643) L2ERC721Bridge_Test:test_bridgeERC721_wrongOwner_reverts() (gas: 29258) L2ERC721Bridge_Test:test_constructor_succeeds() (gas: 10110) L2ERC721Bridge_Test:test_finalizeBridgeERC721_alreadyExists_reverts() (gas: 29128) +L2ERC721Bridge_Test:test_finalizeBridgeERC721_interfaceNotCompliant_reverts() (gas: 236012) L2ERC721Bridge_Test:test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() (gas: 19874) L2ERC721Bridge_Test:test_finalizeBridgeERC721_notViaLocalMessenger_reverts() (gas: 16104) L2ERC721Bridge_Test:test_finalizeBridgeERC721_selfToken_reverts() (gas: 17659) diff --git a/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol b/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol index 49102b17e..1b9332228 100644 --- a/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol @@ -278,6 +278,33 @@ contract L2ERC721Bridge_Test is Messenger_Initializer { assertEq(localToken.ownerOf(tokenId), alice); } + function test_finalizeBridgeERC721_interfaceNotCompliant_reverts() external { + // Create a non-compliant token + NonCompliantERC721 nonCompliantToken = new NonCompliantERC721(alice); + + // Bridge the non-compliant token. + vm.prank(alice); + bridge.bridgeERC721(address(nonCompliantToken), address(0x01), tokenId, 1234, hex"5678"); + + // Attempt to finalize the withdrawal. Should revert because the token does not claim + // to be compliant with the `IOptimismMintableERC721` interface. + vm.mockCall( + address(L2Messenger), + abi.encodeWithSelector(L2Messenger.xDomainMessageSender.selector), + abi.encode(otherBridge) + ); + vm.prank(address(L2Messenger)); + vm.expectRevert("L2ERC721Bridge: local token interface is not compliant"); + bridge.finalizeBridgeERC721( + address(address(nonCompliantToken)), + address(address(0x01)), + alice, + alice, + tokenId, + hex"5678" + ); + } + function test_finalizeBridgeERC721_notViaLocalMessenger_reverts() external { // Finalize a withdrawal. vm.prank(alice); @@ -349,3 +376,30 @@ contract L2ERC721Bridge_Test is Messenger_Initializer { ); } } + +/// @dev A non-compliant ERC721 token that does not implement the full ERC721 interface. +/// This is used to test that the bridge will revert if the token does not claim to support +/// the ERC721 interface. +contract NonCompliantERC721 { + address internal immutable owner; + + constructor(address _owner) { + owner = _owner; + } + + function ownerOf(uint256) external view returns (address) { + return owner; + } + + function remoteToken() external pure returns (address) { + return address(0x01); + } + + function burn(address, uint256) external { + // Do nothing. + } + + function supportsInterface(bytes4) external pure returns (bool) { + return false; + } +} From c9e9656c91b585f1086f853da202894612720f03 Mon Sep 17 00:00:00 2001 From: s7v7nislands Date: Mon, 20 Mar 2023 17:02:42 +0800 Subject: [PATCH 077/404] specs: update DepositFeed to OptimismPortal --- specs/assets/components.svg | 2 +- specs/assets/propagation.svg | 2 +- specs/overview.md | 35 ++++++++++++++++++----------------- specs/withdrawals.md | 27 ++++++++++++++------------- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/specs/assets/components.svg b/specs/assets/components.svg index e2526006c..410204e18 100644 --- a/specs/assets/components.svg +++ b/specs/assets/components.svg @@ -1,4 +1,4 @@ -
DepositFeed
DepositFeed
L2OutputOracle
L2OutputOracle
Rollup
Node
Rollup...
Batch Submitter
Batch Submitter
L2 Execution Engine
(L2 Geth)
L2 Execution Engine...
BatchInbox
BatchInbox
L1
L1
L2
L2
Output Submitter
Output Submitter
Legend
Legend
Sequencer
Sequencer
Verifier
Verifier
Text is not SVG - cannot display
\ No newline at end of file +
OptimismPortal
OptimismPortal
L2OutputOracle
L2OutputOracle
Rollup
Node
Rollup...
Batch Submitter
Batch Submitter
L2 Execution Engine
(L2 Geth)
L2 Execution Engine...
BatchInbox
BatchInbox
L1
L1
L2
L2
Output Submitter
Output Submitter
Legend
Legend
Sequencer
Sequencer
Verifier
Verifier
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/assets/propagation.svg b/specs/assets/propagation.svg index d95494eac..7410bfd96 100644 --- a/specs/assets/propagation.svg +++ b/specs/assets/propagation.svg @@ -1,4 +1,4 @@ -
Engine API
Engine API
Rollup
Node
Rollup...
EE
EE
L1
L1
L2
L2
Batch Submitter
Batch Submitter
Output Submitter
Output Submitter
BatchInbox
BatchInbox
DepositFeed
DepositFeed
Rollup
Node
Rollup...
EE
EE
Unsafe
Block
Propagation
Unsafe...
TX Sync
TX Sync
State Sync
State Sync
Legend
Legend
Sequencer
Sequencer
Verifier
Verifier
Text is not SVG - cannot display
\ No newline at end of file +
Engine API
Engine API
Rollup
Node
Rollup...
EE
EE
L1
L1
L2
L2
Batch Submitter
Batch Submitter
Output Submitter
Output Submitter
BatchInbox
BatchInbox
OptimismPortal
OptimismPortal
Rollup
Node
Rollup...
EE
EE
Unsafe
Block
Propagation
Unsafe...
TX Sync
TX Sync
State Sync
State Sync
Legend
Legend
Sequencer
Sequencer
Verifier
Verifier
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/overview.md b/specs/overview.md index 00fcfd4e5..c51b21966 100644 --- a/specs/overview.md +++ b/specs/overview.md @@ -4,18 +4,19 @@ **Table of Contents** -- [Architecture Design Goals](#architecture-design-goals) -- [Components](#components) - - [L1 Components](#l1-components) - - [L2 Components](#l2-components) - - [Transaction/Block Propagation](#transactionblock-propagation) -- [Key Interactions In Depth](#key-interactions-in-depth) - - [Deposits](#deposits) - - [Block Derivation](#block-derivation) - - [Overview](#overview) - - [Epochs and the Sequencing Window](#epochs-and-the-sequencing-window) - - [Block Derivation Loop](#block-derivation-loop) - - [Engine API](#engine-api) +- [Optimism Overview](#optimism-overview) + - [Architecture Design Goals](#architecture-design-goals) + - [Components](#components) + - [L1 Components](#l1-components) + - [L2 Components](#l2-components) + - [Transaction/Block Propagation](#transactionblock-propagation) + - [Key Interactions In Depth](#key-interactions-in-depth) + - [Deposits](#deposits) + - [Block Derivation](#block-derivation) + - [Overview](#overview) + - [Epochs and the Sequencing Window](#epochs-and-the-sequencing-window) + - [Block Derivation Loop](#block-derivation-loop) + - [Engine API](#engine-api) @@ -48,8 +49,8 @@ mechanisms. ### L1 Components -- **DepositFeed**: A feed of L2 transactions which originated as smart contract calls in the L1 state. - - The `DepositFeed` contract emits `TransactionDeposited` events, which the rollup driver reads in order to process +- **OptimismPortal**: A feed of L2 transactions which originated as smart contract calls in the L1 state. + - The `OptimismPortal` contract emits `TransactionDeposited` events, which the rollup driver reads in order to process deposits. - Deposits are guaranteed to be reflected in the L2 state within the _sequencing window_. - Beware that _transactions_ are deposited, not tokens. However deposited transactions are a key part of implementing @@ -106,7 +107,7 @@ The below diagram illustrates how the sequencer and verifiers fit together: - [Deposits](./deposits.md) Optimism supports two types of deposits: user deposits, and L1 attributes deposits. To perform a user deposit, users -call the `depositTransaction` method on the `DepositFeed` contract. This in turn emits `TransactionDeposited` events, +call the `depositTransaction` method on the `OptimismPortal` contract. This in turn emits `TransactionDeposited` events, which the rollup node reads during block derivation. L1 attributes deposits are used to register L1 block attributes (number, timestamp, etc.) on L2 via a call to the L1 @@ -141,8 +142,8 @@ worth of blocks has passed, i.e. after L1 block number `n + SEQUENCING_WINDOW_SI Each epoch contains at least one block. Every block in the epoch contains an L1 info transaction which contains contextual information about L1 such as the block hash and timestamp. The first block in the epoch also contains all -deposits initiated via the `DepositFeed` contract on L1. All L2 blocks can also contain _sequenced transactions_, i.e. -transactions submitted directly to the sequencer. +deposits initiated via the `OptimismPortal` contract on L1. All L2 blocks can also contain _sequenced transactions_, +i.e. transactions submitted directly to the sequencer. Whenever the sequencer creates a new L2 block for a given epoch, it must submit it to L1 as part of a _batch_, within the epoch's sequencing window (i.e. the batch must land before L1 block `n + SEQUENCING_WINDOW_SIZE`). These batches are diff --git a/specs/withdrawals.md b/specs/withdrawals.md index 84bf4dc09..648247e64 100644 --- a/specs/withdrawals.md +++ b/specs/withdrawals.md @@ -33,18 +33,19 @@ finalization. **Table of Contents** -- [Withdrawal Flow](#withdrawal-flow) - - [On L2](#on-l2) - - [On L1](#on-l1) -- [The L2ToL1MessagePasser Contract](#the-l2tol1messagepasser-contract) - - [Addresses are not Aliased on Withdrawals](#addresses-are-not-aliased-on-withdrawals) -- [The Optimism Portal Contract](#the-optimism-portal-contract) -- [Withdrawal Verification and Finalization](#withdrawal-verification-and-finalization) -- [Security Considerations](#security-considerations) - - [Key Properties of Withdrawal Verification](#key-properties-of-withdrawal-verification) - - [Handling Successfully Verified Messages That Fail When Relayed](#handling-successfully-verified-messages-that-fail-when-relayed) -- [Summary of Definitions](#summary-of-definitions) - - [Constants](#constants) +- [Withdrawals](#withdrawals) + - [Withdrawal Flow](#withdrawal-flow) + - [On L2](#on-l2) + - [On L1](#on-l1) + - [The L2ToL1MessagePasser Contract](#the-l2tol1messagepasser-contract) + - [Addresses are not Aliased on Withdrawals](#addresses-are-not-aliased-on-withdrawals) + - [The Optimism Portal Contract](#the-optimism-portal-contract) + - [Withdrawal Verification and Finalization](#withdrawal-verification-and-finalization) + - [Security Considerations](#security-considerations) + - [Key Properties of Withdrawal Verification](#key-properties-of-withdrawal-verification) + - [Handling Successfully Verified Messages That Fail When Relayed](#handling-successfully-verified-messages-that-fail-when-relayed) + - [Summary of Definitions](#summary-of-definitions) + - [Constants](#constants) @@ -130,7 +131,7 @@ recognize that having the same address does not imply that a contract on L2 will ## The Optimism Portal Contract The Optimism Portal serves as both the entry and exit point to the Optimism L2. It is a contract which inherits from -the [DepositFeed](./deposits.md#deposit-contract) contract, and in addition provides the following interface for +the [OptimismPortal](./deposits.md#deposit-contract) contract, and in addition provides the following interface for withdrawals: - [`WithdrawalTransaction` type] From 2aadc2dd72fa81fcc13cd3ae9d0119f78bc009c0 Mon Sep 17 00:00:00 2001 From: s7v7nislands Date: Mon, 20 Mar 2023 17:09:35 +0800 Subject: [PATCH 078/404] specs: update toc --- specs/overview.md | 25 ++++++++++++------------- specs/withdrawals.md | 25 ++++++++++++------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/specs/overview.md b/specs/overview.md index c51b21966..7a5cdd952 100644 --- a/specs/overview.md +++ b/specs/overview.md @@ -4,19 +4,18 @@ **Table of Contents** -- [Optimism Overview](#optimism-overview) - - [Architecture Design Goals](#architecture-design-goals) - - [Components](#components) - - [L1 Components](#l1-components) - - [L2 Components](#l2-components) - - [Transaction/Block Propagation](#transactionblock-propagation) - - [Key Interactions In Depth](#key-interactions-in-depth) - - [Deposits](#deposits) - - [Block Derivation](#block-derivation) - - [Overview](#overview) - - [Epochs and the Sequencing Window](#epochs-and-the-sequencing-window) - - [Block Derivation Loop](#block-derivation-loop) - - [Engine API](#engine-api) +- [Architecture Design Goals](#architecture-design-goals) +- [Components](#components) + - [L1 Components](#l1-components) + - [L2 Components](#l2-components) + - [Transaction/Block Propagation](#transactionblock-propagation) +- [Key Interactions In Depth](#key-interactions-in-depth) + - [Deposits](#deposits) + - [Block Derivation](#block-derivation) + - [Overview](#overview) + - [Epochs and the Sequencing Window](#epochs-and-the-sequencing-window) + - [Block Derivation Loop](#block-derivation-loop) + - [Engine API](#engine-api) diff --git a/specs/withdrawals.md b/specs/withdrawals.md index 648247e64..61bd8e45e 100644 --- a/specs/withdrawals.md +++ b/specs/withdrawals.md @@ -33,19 +33,18 @@ finalization. **Table of Contents** -- [Withdrawals](#withdrawals) - - [Withdrawal Flow](#withdrawal-flow) - - [On L2](#on-l2) - - [On L1](#on-l1) - - [The L2ToL1MessagePasser Contract](#the-l2tol1messagepasser-contract) - - [Addresses are not Aliased on Withdrawals](#addresses-are-not-aliased-on-withdrawals) - - [The Optimism Portal Contract](#the-optimism-portal-contract) - - [Withdrawal Verification and Finalization](#withdrawal-verification-and-finalization) - - [Security Considerations](#security-considerations) - - [Key Properties of Withdrawal Verification](#key-properties-of-withdrawal-verification) - - [Handling Successfully Verified Messages That Fail When Relayed](#handling-successfully-verified-messages-that-fail-when-relayed) - - [Summary of Definitions](#summary-of-definitions) - - [Constants](#constants) +- [Withdrawal Flow](#withdrawal-flow) + - [On L2](#on-l2) + - [On L1](#on-l1) +- [The L2ToL1MessagePasser Contract](#the-l2tol1messagepasser-contract) + - [Addresses are not Aliased on Withdrawals](#addresses-are-not-aliased-on-withdrawals) +- [The Optimism Portal Contract](#the-optimism-portal-contract) +- [Withdrawal Verification and Finalization](#withdrawal-verification-and-finalization) +- [Security Considerations](#security-considerations) + - [Key Properties of Withdrawal Verification](#key-properties-of-withdrawal-verification) + - [Handling Successfully Verified Messages That Fail When Relayed](#handling-successfully-verified-messages-that-fail-when-relayed) +- [Summary of Definitions](#summary-of-definitions) + - [Constants](#constants) From 0fd7076a4fbdd019a2d7c81391c33c4abcedc54f Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:04:15 +0100 Subject: [PATCH 079/404] op-node/rollup/derive: Add L2BlockToBlockRef This is similar to PayloadToBlockRef but takes an interface instead that e.g. a types.Block fulfills. This way, we can directly extract L2BlockRefs from type.Blocks that have a valid L1 Info Deposit tx as first tx. A block could also be converted into an ExecutionPayload, but this would make an inefficient roundtrip, marshalling and unmarshalling all transactions. --- op-node/rollup/derive/l2block_util.go | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 op-node/rollup/derive/l2block_util.go diff --git a/op-node/rollup/derive/l2block_util.go b/op-node/rollup/derive/l2block_util.go new file mode 100644 index 000000000..179b10216 --- /dev/null +++ b/op-node/rollup/derive/l2block_util.go @@ -0,0 +1,64 @@ +package derive + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup" +) + +// L2BlockRefSource is a source for the generation of a L2BlockRef. E.g. a +// *types.Block is a L2BlockRefSource. +// +// L2BlockToBlockRef extracts L2BlockRef from a L2BlockRefSource. The first +// transaction of a source must be a Deposit transaction. +type L2BlockRefSource interface { + Hash() common.Hash + ParentHash() common.Hash + NumberU64() uint64 + Time() uint64 + Transactions() types.Transactions +} + +// PayloadToBlockRef extracts the essential L2BlockRef information from an L2 +// block ref source, falling back to genesis information if necessary. +func L2BlockToBlockRef(block L2BlockRefSource, genesis *rollup.Genesis) (eth.L2BlockRef, error) { + hash, number := block.Hash(), block.NumberU64() + + var l1Origin eth.BlockID + var sequenceNumber uint64 + if number == genesis.L2.Number { + if hash != genesis.L2.Hash { + return eth.L2BlockRef{}, fmt.Errorf("expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", genesis.L2.Number, hash, genesis.L2.Hash) + } + l1Origin = genesis.L1 + sequenceNumber = 0 + } else { + txs := block.Transactions() + if txs.Len() == 0 { + return eth.L2BlockRef{}, fmt.Errorf("l2 block is missing L1 info deposit tx, block hash: %s", hash) + } + tx := txs[0] + if tx.Type() != types.DepositTxType { + return eth.L2BlockRef{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type()) + } + info, err := L1InfoDepositTxData(tx.Data()) + if err != nil { + return eth.L2BlockRef{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) + } + l1Origin = eth.BlockID{Hash: info.BlockHash, Number: info.Number} + sequenceNumber = info.SequenceNumber + } + + return eth.L2BlockRef{ + Hash: hash, + Number: number, + ParentHash: block.ParentHash(), + Time: block.Time(), + L1Origin: l1Origin, + SequenceNumber: sequenceNumber, + }, nil +} From 2c2f2c1d5f32ac67fd5d58241b95d4852840b37e Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:06:54 +0100 Subject: [PATCH 080/404] op-node/rollup/derive: Let BlockToBatch also return L1BlockInfo The caller of BlockToBatch is sometimes interested in the L1BlockInfo of the block, so instead of re-extracting it again, we just return it here. --- op-node/rollup/derive/channel_out.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index feef8ae7c..f644aab68 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -76,7 +76,7 @@ func (co *ChannelOut) AddBlock(block *types.Block) (uint64, error) { return 0, errors.New("already closed") } - batch, err := BlockToBatch(block) + batch, _, err := BlockToBatch(block) if err != nil { return 0, err } @@ -182,7 +182,7 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, erro } // BlockToBatch transforms a block into a batch object that can easily be RLP encoded. -func BlockToBatch(block *types.Block) (*BatchData, error) { +func BlockToBatch(block *types.Block) (*BatchData, L1BlockInfo, error) { opaqueTxs := make([]hexutil.Bytes, 0, len(block.Transactions())) for i, tx := range block.Transactions() { if tx.Type() == types.DepositTxType { @@ -190,17 +190,17 @@ func BlockToBatch(block *types.Block) (*BatchData, error) { } otx, err := tx.MarshalBinary() if err != nil { - return nil, fmt.Errorf("could not encode tx %v in block %v: %w", i, tx.Hash(), err) + return nil, L1BlockInfo{}, fmt.Errorf("could not encode tx %v in block %v: %w", i, tx.Hash(), err) } opaqueTxs = append(opaqueTxs, otx) } l1InfoTx := block.Transactions()[0] if l1InfoTx.Type() != types.DepositTxType { - return nil, ErrNotDepositTx + return nil, L1BlockInfo{}, ErrNotDepositTx } l1Info, err := L1InfoDepositTxData(l1InfoTx.Data()) if err != nil { - return nil, fmt.Errorf("could not parse the L1 Info deposit: %w", err) + return nil, l1Info, fmt.Errorf("could not parse the L1 Info deposit: %w", err) } return &BatchData{ @@ -211,7 +211,7 @@ func BlockToBatch(block *types.Block) (*BatchData, error) { Timestamp: block.Time(), Transactions: opaqueTxs, }, - }, nil + }, l1Info, nil } // ForceCloseTxData generates the transaction data for a transaction which will force close From 4751b03c42990bdadfbc26e916c87bee5d49b8e2 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:10:36 +0100 Subject: [PATCH 081/404] op-service/metrics: Add RefMetrics RefMetrics are a reusable metrics module to enable block reference metrics. --- op-service/metrics/ref_metrics.go | 118 ++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 op-service/metrics/ref_metrics.go diff --git a/op-service/metrics/ref_metrics.go b/op-service/metrics/ref_metrics.go new file mode 100644 index 000000000..159e788b2 --- /dev/null +++ b/op-service/metrics/ref_metrics.go @@ -0,0 +1,118 @@ +package metrics + +import ( + "encoding/binary" + "time" + + "github.com/ethereum-optimism/optimism/op-node/eth" + + "github.com/ethereum/go-ethereum/common" + "github.com/prometheus/client_golang/prometheus" +) + +type RefMetricer interface { + RecordRef(layer string, name string, num uint64, timestamp uint64, h common.Hash) + RecordL1Ref(name string, ref eth.L1BlockRef) + RecordL2Ref(name string, ref eth.L2BlockRef) +} + +// RefMetrics provides block reference metrics. It's a metrics module that's +// supposed to be embedded into a service metrics type. The service metrics type +// should set the full namespace and create the factory before calling +// NewRefMetrics. +type RefMetrics struct { + RefsNumber *prometheus.GaugeVec + RefsTime *prometheus.GaugeVec + RefsHash *prometheus.GaugeVec + RefsSeqNr *prometheus.GaugeVec + RefsLatency *prometheus.GaugeVec + // hash of the last seen block per name, so we don't reduce/increase latency on updates of the same data, + // and only count the first occurrence + LatencySeen map[string]common.Hash +} + +var _ RefMetricer = (*RefMetrics)(nil) + +// MakeRefMetrics returns a new RefMetrics, initializing its prometheus fields +// using factory. It is supposed to be used inside the construtors of metrics +// structs for any op service after the full namespace and factory have been +// setup. +// +// ns is the fully qualified namespace, e.g. "op_node_default". +func MakeRefMetrics(ns string, factory Factory) RefMetrics { + return RefMetrics{ + RefsNumber: factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "refs_number", + Help: "Gauge representing the different L1/L2 reference block numbers", + }, []string{ + "layer", + "type", + }), + RefsTime: factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "refs_time", + Help: "Gauge representing the different L1/L2 reference block timestamps", + }, []string{ + "layer", + "type", + }), + RefsHash: factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "refs_hash", + Help: "Gauge representing the different L1/L2 reference block hashes truncated to float values", + }, []string{ + "layer", + "type", + }), + RefsSeqNr: factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "refs_seqnr", + Help: "Gauge representing the different L2 reference sequence numbers", + }, []string{ + "type", + }), + RefsLatency: factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "refs_latency", + Help: "Gauge representing the different L1/L2 reference block timestamps minus current time, in seconds", + }, []string{ + "layer", + "type", + }), + LatencySeen: make(map[string]common.Hash), + } +} + +func (m *RefMetrics) RecordRef(layer string, name string, num uint64, timestamp uint64, h common.Hash) { + m.RefsNumber.WithLabelValues(layer, name).Set(float64(num)) + if timestamp != 0 { + m.RefsTime.WithLabelValues(layer, name).Set(float64(timestamp)) + // only meter the latency when we first see this hash for the given label name + if m.LatencySeen[name] != h { + m.LatencySeen[name] = h + m.RefsLatency.WithLabelValues(layer, name).Set(float64(timestamp) - (float64(time.Now().UnixNano()) / 1e9)) + } + } + // we map the first 8 bytes to a float64, so we can graph changes of the hash to find divergences visually. + // We don't do math.Float64frombits, just a regular conversion, to keep the value within a manageable range. + m.RefsHash.WithLabelValues(layer, name).Set(float64(binary.LittleEndian.Uint64(h[:]))) +} + +func (m *RefMetrics) RecordL1Ref(name string, ref eth.L1BlockRef) { + m.RecordRef("l1", name, ref.Number, ref.Time, ref.Hash) +} + +func (m *RefMetrics) RecordL2Ref(name string, ref eth.L2BlockRef) { + m.RecordRef("l2", name, ref.Number, ref.Time, ref.Hash) + m.RecordRef("l1_origin", name, ref.L1Origin.Number, 0, ref.L1Origin.Hash) + m.RefsSeqNr.WithLabelValues(name).Set(float64(ref.SequenceNumber)) +} + +// NoopRefMetrics can be embedded in a noop version of a metric implementation +// to have a noop RefMetricer. +type NoopRefMetrics struct{} + +func (*NoopRefMetrics) RecordRef(string, string, uint64, uint64, common.Hash) {} +func (*NoopRefMetrics) RecordL1Ref(string, eth.L1BlockRef) {} +func (*NoopRefMetrics) RecordL2Ref(string, eth.L2BlockRef) {} From cc2c0b4e4a9a628c1388c2023569a8f936d38e9a Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:12:25 +0100 Subject: [PATCH 082/404] op-service/metrics: Add Event and EventVec metrics Reusable event metrics modules. --- op-service/metrics/event.go | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 op-service/metrics/event.go diff --git a/op-service/metrics/event.go b/op-service/metrics/event.go new file mode 100644 index 000000000..8589bf654 --- /dev/null +++ b/op-service/metrics/event.go @@ -0,0 +1,58 @@ +package metrics + +import ( + "fmt" + + "github.com/prometheus/client_golang/prometheus" +) + +type Event struct { + Total prometheus.Counter + LastTime prometheus.Gauge +} + +func (e *Event) Record() { + e.Total.Inc() + e.LastTime.SetToCurrentTime() +} + +func NewEvent(factory Factory, ns string, name string, displayName string) Event { + return Event{ + Total: factory.NewCounter(prometheus.CounterOpts{ + Namespace: ns, + Name: fmt.Sprintf("%s_total", name), + Help: fmt.Sprintf("Count of %s events", displayName), + }), + LastTime: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: fmt.Sprintf("last_%s_unix", name), + Help: fmt.Sprintf("Timestamp of last %s event", displayName), + }), + } +} + +type EventVec struct { + Total prometheus.CounterVec + LastTime prometheus.GaugeVec +} + +func (e *EventVec) Record(lvs ...string) { + e.Total.WithLabelValues(lvs...).Inc() + e.LastTime.WithLabelValues(lvs...).SetToCurrentTime() +} + +func NewEventVec(factory Factory, ns string, name string, displayName string, labelNames []string) EventVec { + return EventVec{ + Total: *factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: fmt.Sprintf("%s_total", name), + Help: fmt.Sprintf("Count of %s events", displayName), + }, labelNames), + LastTime: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: fmt.Sprintf("last_%s_unix", name), + Help: fmt.Sprintf("Timestamp of last %s event", displayName), + }, + labelNames), + } +} From b18b14910d22236e9d12fb302a1f49c241065b82 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:14:33 +0100 Subject: [PATCH 083/404] op-batcher: Let channelBuilder.AddBlock return L1BlockInfo --- op-batcher/batcher/channel_builder.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 070c4a0e9..788770ac7 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -185,21 +185,21 @@ func (c *channelBuilder) Reset() error { // already full. See description of FullErr for details. // // Call OutputFrames() afterwards to create frames. -func (c *channelBuilder) AddBlock(block *types.Block) error { +func (c *channelBuilder) AddBlock(block *types.Block) (derive.L1BlockInfo, error) { if c.IsFull() { - return c.FullErr() + return derive.L1BlockInfo{}, c.FullErr() } - batch, err := derive.BlockToBatch(block) + batch, l1info, err := derive.BlockToBatch(block) if err != nil { - return fmt.Errorf("converting block to batch: %w", err) + return l1info, fmt.Errorf("converting block to batch: %w", err) } if _, err = c.co.AddBatch(batch); errors.Is(err, derive.ErrTooManyRLPBytes) { c.setFullErr(err) - return c.FullErr() + return l1info, c.FullErr() } else if err != nil { - return fmt.Errorf("adding block to channel out: %w", err) + return l1info, fmt.Errorf("adding block to channel out: %w", err) } c.blocks = append(c.blocks, block) c.updateSwTimeout(batch) @@ -209,7 +209,7 @@ func (c *channelBuilder) AddBlock(block *types.Block) error { // Adding this block still worked, so don't return error, just mark as full } - return nil + return l1info, nil } // Timeout management From 5171e91f8c75e1c086d17320cfa8923dab66d2ba Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:15:00 +0100 Subject: [PATCH 084/404] op-batcher: Add output bytes tracking to channelBuilder --- op-batcher/batcher/channel_builder.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 788770ac7..b5c5b1d3b 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -136,6 +136,8 @@ type channelBuilder struct { blocks []*types.Block // frames data queue, to be send as txs frames []frameData + // total amount of output data of all frames created yet + outputBytes int } // newChannelBuilder creates a new channel builder or returns an error if the @@ -156,11 +158,21 @@ func (c *channelBuilder) ID() derive.ChannelID { return c.co.ID() } -// InputBytes returns to total amount of input bytes added to the channel. +// InputBytes returns the total amount of input bytes added to the channel. func (c *channelBuilder) InputBytes() int { return c.co.InputBytes() } +// ReadyBytes returns the amount of bytes ready in the compression pipeline to +// output into a frame. +func (c *channelBuilder) ReadyBytes() int { + return c.co.ReadyBytes() +} + +func (c *channelBuilder) OutputBytes() int { + return c.outputBytes +} + // Blocks returns a backup list of all blocks that were added to the channel. It // can be used in case the channel needs to be rebuilt. func (c *channelBuilder) Blocks() []*types.Block { @@ -381,10 +393,11 @@ func (c *channelBuilder) outputFrame() error { } frame := frameData{ - id: txID{chID: c.co.ID(), frameNumber: fn}, + id: frameID{chID: c.co.ID(), frameNumber: fn}, data: buf.Bytes(), } c.frames = append(c.frames, frame) + c.outputBytes += len(frame.data) return err // possibly io.EOF (last frame) } From 10d27cc1c02c06ea1bf40444d4f68296e4d68300 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:35:51 +0100 Subject: [PATCH 085/404] op-batcher/metrics: Add batcher metrics package --- op-batcher/metrics/metrics.go | 249 ++++++++++++++++++++++++++++++++++ op-batcher/metrics/noop.go | 30 ++++ 2 files changed, 279 insertions(+) create mode 100644 op-batcher/metrics/metrics.go create mode 100644 op-batcher/metrics/noop.go diff --git a/op-batcher/metrics/metrics.go b/op-batcher/metrics/metrics.go new file mode 100644 index 000000000..f7093a6e8 --- /dev/null +++ b/op-batcher/metrics/metrics.go @@ -0,0 +1,249 @@ +package metrics + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/prometheus/client_golang/prometheus" + + "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" +) + +const Namespace = "op_batcher" + +type Metricer interface { + RecordInfo(version string) + RecordUp() + + // Records all L1 and L2 block events + opmetrics.RefMetricer + + RecordLatestL1Block(l1ref eth.L1BlockRef) + RecordL2BlocksLoaded(l2ref eth.L2BlockRef) + RecordChannelOpened(id derive.ChannelID, numPendingBlocks int) + RecordL2BlocksAdded(l2ref eth.L2BlockRef, numBlocksAdded, numPendingBlocks, inputBytes, outputComprBytes int) + RecordChannelClosed(id derive.ChannelID, numPendingBlocks int, numFrames int, inputBytes int, outputComprBytes int, reason error) + RecordChannelFullySubmitted(id derive.ChannelID) + RecordChannelTimedOut(id derive.ChannelID) + + RecordBatchTxSubmitted() + RecordBatchTxSuccess() + RecordBatchTxFailed() + + Document() []opmetrics.DocumentedMetric +} + +type Metrics struct { + ns string + registry *prometheus.Registry + factory opmetrics.Factory + + opmetrics.RefMetrics + + Info prometheus.GaugeVec + Up prometheus.Gauge + + // label by openend, closed, fully_submitted, timed_out + ChannelEvs opmetrics.EventVec + + PendingBlocksCount prometheus.GaugeVec + BlocksAddedCount prometheus.Gauge + + ChannelInputBytes prometheus.GaugeVec + ChannelReadyBytes prometheus.Gauge + ChannelOutputBytes prometheus.Gauge + ChannelClosedReason prometheus.Gauge + ChannelNumFrames prometheus.Gauge + ChannelComprRatio prometheus.Histogram + + BatcherTxEvs opmetrics.EventVec +} + +var _ Metricer = (*Metrics)(nil) + +func NewMetrics(procName string) *Metrics { + if procName == "" { + procName = "default" + } + ns := Namespace + "_" + procName + + registry := opmetrics.NewRegistry() + factory := opmetrics.With(registry) + + return &Metrics{ + ns: ns, + registry: registry, + factory: factory, + + RefMetrics: opmetrics.MakeRefMetrics(ns, factory), + + Info: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "info", + Help: "Pseudo-metric tracking version and config info", + }, []string{ + "version", + }), + Up: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "up", + Help: "1 if the op-batcher has finished starting up", + }), + + ChannelEvs: opmetrics.NewEventVec(factory, ns, "channel", "Channel", []string{"stage"}), + + PendingBlocksCount: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "pending_blocks_count", + Help: "Number of pending blocks, not added to a channel yet.", + }, []string{"stage"}), + BlocksAddedCount: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "blocks_added_count", + Help: "Total number of blocks added to current channel.", + }), + + ChannelInputBytes: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "input_bytes", + Help: "Number of input bytes to a channel.", + }, []string{"stage"}), + ChannelReadyBytes: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "ready_bytes", + Help: "Number of bytes ready in the compression buffer.", + }), + ChannelOutputBytes: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "output_bytes", + Help: "Number of compressed output bytes from a channel.", + }), + ChannelClosedReason: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "channel_closed_reason", + Help: "Pseudo-metric to record the reason a channel got closed.", + }), + ChannelNumFrames: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "channel_num_frames", + Help: "Total number of frames of closed channel.", + }), + ChannelComprRatio: factory.NewHistogram(prometheus.HistogramOpts{ + Namespace: ns, + Name: "channel_compr_ratio", + Help: "Compression ratios of closed channel.", + Buckets: append([]float64{0.1, 0.2}, prometheus.LinearBuckets(0.3, 0.05, 14)...), + }), + + BatcherTxEvs: opmetrics.NewEventVec(factory, ns, "batcher_tx", "BatcherTx", []string{"stage"}), + } +} + +func (m *Metrics) Serve(ctx context.Context, host string, port int) error { + return opmetrics.ListenAndServe(ctx, m.registry, host, port) +} + +func (m *Metrics) Document() []opmetrics.DocumentedMetric { + return m.factory.Document() +} + +func (m *Metrics) StartBalanceMetrics(ctx context.Context, + l log.Logger, client *ethclient.Client, account common.Address) { + opmetrics.LaunchBalanceMetrics(ctx, l, m.registry, m.ns, client, account) +} + +// RecordInfo sets a pseudo-metric that contains versioning and +// config info for the op-batcher. +func (m *Metrics) RecordInfo(version string) { + m.Info.WithLabelValues(version).Set(1) +} + +// RecordUp sets the up metric to 1. +func (m *Metrics) RecordUp() { + prometheus.MustRegister() + m.Up.Set(1) +} + +const ( + StageLoaded = "loaded" + StageOpened = "opened" + StageAdded = "added" + StageClosed = "closed" + StageFullySubmitted = "fully_submitted" + StageTimedOut = "timed_out" + + TxStageSubmitted = "submitted" + TxStageSuccess = "success" + TxStageFailed = "failed" +) + +func (m *Metrics) RecordLatestL1Block(l1ref eth.L1BlockRef) { + m.RecordL1Ref("latest", l1ref) +} + +// RecordL2BlockLoaded should be called when a new L2 block was loaded into the +// channel manager (but not processed yet). +func (m *Metrics) RecordL2BlocksLoaded(l2ref eth.L2BlockRef) { + m.RecordL2Ref(StageLoaded, l2ref) +} + +func (m *Metrics) RecordChannelOpened(id derive.ChannelID, numPendingBlocks int) { + m.ChannelEvs.Record(StageOpened) + m.BlocksAddedCount.Set(0) // reset + m.PendingBlocksCount.WithLabelValues(StageOpened).Set(float64(numPendingBlocks)) +} + +// RecordL2BlocksAdded should be called when L2 block were added to the channel +// builder, with the latest added block. +func (m *Metrics) RecordL2BlocksAdded(l2ref eth.L2BlockRef, numBlocksAdded, numPendingBlocks, inputBytes, outputComprBytes int) { + m.RecordL2Ref(StageAdded, l2ref) + m.BlocksAddedCount.Add(float64(numBlocksAdded)) + m.PendingBlocksCount.WithLabelValues(StageAdded).Set(float64(numPendingBlocks)) + m.ChannelInputBytes.WithLabelValues(StageAdded).Set(float64(inputBytes)) + m.ChannelReadyBytes.Set(float64(outputComprBytes)) +} + +func (m *Metrics) RecordChannelClosed(id derive.ChannelID, numPendingBlocks int, numFrames int, inputBytes int, outputComprBytes int, reason error) { + m.ChannelEvs.Record(StageClosed) + m.PendingBlocksCount.WithLabelValues(StageClosed).Set(float64(numPendingBlocks)) + m.ChannelNumFrames.Set(float64(numFrames)) + m.ChannelInputBytes.WithLabelValues(StageClosed).Set(float64(inputBytes)) + m.ChannelOutputBytes.Set(float64(outputComprBytes)) + + var comprRatio float64 + if inputBytes > 0 { + comprRatio = float64(outputComprBytes) / float64(inputBytes) + } + m.ChannelComprRatio.Observe(comprRatio) + + m.ChannelClosedReason.Set(float64(ClosedReasonToNum(reason))) +} + +func ClosedReasonToNum(reason error) int { + // CLI-3640 + return 0 +} + +func (m *Metrics) RecordChannelFullySubmitted(id derive.ChannelID) { + m.ChannelEvs.Record(StageFullySubmitted) +} + +func (m *Metrics) RecordChannelTimedOut(id derive.ChannelID) { + m.ChannelEvs.Record(StageTimedOut) +} + +func (m *Metrics) RecordBatchTxSubmitted() { + m.BatcherTxEvs.Record(TxStageSubmitted) +} + +func (m *Metrics) RecordBatchTxSuccess() { + m.BatcherTxEvs.Record(TxStageSuccess) +} + +func (m *Metrics) RecordBatchTxFailed() { + m.BatcherTxEvs.Record(TxStageFailed) +} diff --git a/op-batcher/metrics/noop.go b/op-batcher/metrics/noop.go new file mode 100644 index 000000000..80f993f08 --- /dev/null +++ b/op-batcher/metrics/noop.go @@ -0,0 +1,30 @@ +package metrics + +import ( + "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" +) + +type noopMetrics struct{ opmetrics.NoopRefMetrics } + +var NoopMetrics Metricer = new(noopMetrics) + +func (*noopMetrics) Document() []opmetrics.DocumentedMetric { return nil } + +func (*noopMetrics) RecordInfo(version string) {} +func (*noopMetrics) RecordUp() {} + +func (*noopMetrics) RecordLatestL1Block(l1ref eth.L1BlockRef) {} +func (*noopMetrics) RecordL2BlocksLoaded(eth.L2BlockRef) {} +func (*noopMetrics) RecordChannelOpened(derive.ChannelID, int) {} +func (*noopMetrics) RecordL2BlocksAdded(eth.L2BlockRef, int, int, int, int) {} + +func (*noopMetrics) RecordChannelClosed(derive.ChannelID, int, int, int, int, error) {} + +func (*noopMetrics) RecordChannelFullySubmitted(derive.ChannelID) {} +func (*noopMetrics) RecordChannelTimedOut(derive.ChannelID) {} + +func (*noopMetrics) RecordBatchTxSubmitted() {} +func (*noopMetrics) RecordBatchTxSuccess() {} +func (*noopMetrics) RecordBatchTxFailed() {} From b4929fa1967ecc1c600a8ef10eec10ec03963eed Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 15 Mar 2023 23:47:22 +0100 Subject: [PATCH 086/404] op-batcher: Add metrics to batcher --- op-batcher/batcher/batch_submitter.go | 13 ++- op-batcher/batcher/channel_builder_test.go | 6 +- op-batcher/batcher/channel_manager.go | 111 +++++++++++++++++---- op-batcher/batcher/channel_manager_test.go | 35 ++++--- op-batcher/batcher/config.go | 18 ++-- op-batcher/batcher/driver.go | 55 +++++++--- op-e2e/migration_test.go | 3 +- op-e2e/setup.go | 3 +- 8 files changed, 177 insertions(+), 67 deletions(-) diff --git a/op-batcher/batcher/batch_submitter.go b/op-batcher/batcher/batch_submitter.go index 964b92550..132660614 100644 --- a/op-batcher/batcher/batch_submitter.go +++ b/op-batcher/batcher/batch_submitter.go @@ -12,9 +12,9 @@ import ( gethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/urfave/cli" + "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/rpc" oplog "github.com/ethereum-optimism/optimism/op-service/log" - opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" ) @@ -36,9 +36,10 @@ func Main(version string, cliCtx *cli.Context) error { } l := oplog.NewLogger(cfg.LogConfig) + m := metrics.NewMetrics("default") l.Info("Initializing Batch Submitter") - batchSubmitter, err := NewBatchSubmitterFromCLIConfig(cfg, l) + batchSubmitter, err := NewBatchSubmitterFromCLIConfig(cfg, l, m) if err != nil { l.Error("Unable to create Batch Submitter", "error", err) return err @@ -64,16 +65,15 @@ func Main(version string, cliCtx *cli.Context) error { }() } - registry := opmetrics.NewRegistry() metricsCfg := cfg.MetricsConfig if metricsCfg.Enabled { l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) go func() { - if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { + if err := m.Serve(ctx, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { l.Error("error starting metrics server", err) } }() - opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", batchSubmitter.L1Client, batchSubmitter.From) + m.StartBalanceMetrics(ctx, l, batchSubmitter.L1Client, batchSubmitter.From) } rpcCfg := cfg.RPCConfig @@ -95,6 +95,9 @@ func Main(version string, cliCtx *cli.Context) error { return fmt.Errorf("error starting RPC server: %w", err) } + m.RecordInfo(version) + m.RecordUp() + interruptChannel := make(chan os.Signal, 1) signal.Notify(interruptChannel, []os.Signal{ os.Interrupt, diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 16854a09b..1ea702232 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -68,7 +68,7 @@ func addNonsenseBlock(cb *channelBuilder) error { a := types.NewBlock(&types.Header{ Number: big.NewInt(0), }, txs, nil, nil, trie.NewStackTrie(nil)) - err = cb.AddBlock(a) + _, err = cb.AddBlock(a) return err } @@ -98,7 +98,7 @@ func buildTooLargeRlpEncodedBlockBatch(cb *channelBuilder) error { // When a batch is constructed from the block and // then rlp encoded in the channel out, the size // will exceed [derive.MaxRLPBytesPerChannel] - err := cb.AddBlock(block) + _, err := cb.AddBlock(block) return err } @@ -462,7 +462,7 @@ func TestOutputFramesMaxFrameIndex(t *testing.T) { a := types.NewBlock(&types.Header{ Number: big.NewInt(0), }, txs, nil, nil, trie.NewStackTrie(nil)) - err = cb.AddBlock(a) + _, err = cb.AddBlock(a) if cb.IsFull() { fullErr := cb.FullErr() require.ErrorIs(t, fullErr, ErrMaxFrameIndex) diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index c2c64dcce..270d60395 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -6,7 +6,9 @@ import ( "io" "math" + "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -22,8 +24,9 @@ var ErrReorg = errors.New("block does not extend existing chain") // channel. // Functions on channelManager are not safe for concurrent access. type channelManager struct { - log log.Logger - cfg ChannelConfig + log log.Logger + metr metrics.Metricer + cfg ChannelConfig // All blocks since the last request for new tx data. blocks []*types.Block @@ -40,10 +43,12 @@ type channelManager struct { confirmedTransactions map[txID]eth.BlockID } -func NewChannelManager(log log.Logger, cfg ChannelConfig) *channelManager { +func NewChannelManager(log log.Logger, metr metrics.Metricer, cfg ChannelConfig) *channelManager { return &channelManager{ - log: log, - cfg: cfg, + log: log, + metr: metr, + cfg: cfg, + pendingTransactions: make(map[txID]txData), confirmedTransactions: make(map[txID]eth.BlockID), } @@ -71,6 +76,8 @@ func (s *channelManager) TxFailed(id txID) { } else { s.log.Warn("unknown transaction marked as failed", "id", id) } + + s.metr.RecordBatchTxFailed() } // TxConfirmed marks a transaction as confirmed on L1. Unfortunately even if all frames in @@ -78,7 +85,8 @@ func (s *channelManager) TxFailed(id txID) { // resubmitted. // This function may reset the pending channel if the pending channel has timed out. func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) { - s.log.Trace("marked transaction as confirmed", "id", id, "block", inclusionBlock) + s.metr.RecordBatchTxSubmitted() + s.log.Debug("marked transaction as confirmed", "id", id, "block", inclusionBlock) if _, ok := s.pendingTransactions[id]; !ok { s.log.Warn("unknown transaction marked as confirmed", "id", id, "block", inclusionBlock) // TODO: This can occur if we clear the channel while there are still pending transactions @@ -92,13 +100,15 @@ func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) { // If this channel timed out, put the pending blocks back into the local saved blocks // and then reset this state so it can try to build a new channel. if s.pendingChannelIsTimedOut() { - s.log.Warn("Channel timed out", "chID", s.pendingChannel.ID()) + s.metr.RecordChannelTimedOut(s.pendingChannel.ID()) + s.log.Warn("Channel timed out", "id", s.pendingChannel.ID()) s.blocks = append(s.pendingChannel.Blocks(), s.blocks...) s.clearPendingChannel() } // If we are done with this channel, record that. if s.pendingChannelIsFullySubmitted() { - s.log.Info("Channel is fully submitted", "chID", s.pendingChannel.ID()) + s.metr.RecordChannelFullySubmitted(s.pendingChannel.ID()) + s.log.Info("Channel is fully submitted", "id", s.pendingChannel.ID()) s.clearPendingChannel() } } @@ -194,8 +204,8 @@ func (s *channelManager) TxData(l1Head eth.BlockID) (txData, error) { // all pending blocks be included in this channel for submission. s.registerL1Block(l1Head) - if err := s.pendingChannel.OutputFrames(); err != nil { - return txData{}, fmt.Errorf("creating frames with channel builder: %w", err) + if err := s.outputFrames(); err != nil { + return txData{}, err } return s.nextTxData() @@ -211,7 +221,11 @@ func (s *channelManager) ensurePendingChannel(l1Head eth.BlockID) error { return fmt.Errorf("creating new channel: %w", err) } s.pendingChannel = cb - s.log.Info("Created channel", "chID", cb.ID(), "l1Head", l1Head) + s.log.Info("Created channel", + "id", cb.ID(), + "l1Head", l1Head, + "blocks_pending", len(s.blocks)) + s.metr.RecordChannelOpened(cb.ID(), len(s.blocks)) return nil } @@ -229,28 +243,27 @@ func (s *channelManager) registerL1Block(l1Head eth.BlockID) { // processBlocks adds blocks from the blocks queue to the pending channel until // either the queue got exhausted or the channel is full. func (s *channelManager) processBlocks() error { - var blocksAdded int - var _chFullErr *ChannelFullError // throw away, just for type checking + var ( + blocksAdded int + _chFullErr *ChannelFullError // throw away, just for type checking + latestL2ref eth.L2BlockRef + ) for i, block := range s.blocks { - if err := s.pendingChannel.AddBlock(block); errors.As(err, &_chFullErr) { + l1info, err := s.pendingChannel.AddBlock(block) + if errors.As(err, &_chFullErr) { // current block didn't get added because channel is already full break } else if err != nil { return fmt.Errorf("adding block[%d] to channel builder: %w", i, err) } blocksAdded += 1 + latestL2ref = l2BlockRefFromBlockAndL1Info(block, l1info) // current block got added but channel is now full if s.pendingChannel.IsFull() { break } } - s.log.Debug("Added blocks to channel", - "blocks_added", blocksAdded, - "channel_full", s.pendingChannel.IsFull(), - "blocks_pending", len(s.blocks)-blocksAdded, - "input_bytes", s.pendingChannel.InputBytes(), - ) if blocksAdded == len(s.blocks) { // all blocks processed, reuse slice s.blocks = s.blocks[:0] @@ -258,6 +271,53 @@ func (s *channelManager) processBlocks() error { // remove processed blocks s.blocks = s.blocks[blocksAdded:] } + + s.metr.RecordL2BlocksAdded(latestL2ref, + blocksAdded, + len(s.blocks), + s.pendingChannel.InputBytes(), + s.pendingChannel.ReadyBytes()) + s.log.Debug("Added blocks to channel", + "blocks_added", blocksAdded, + "blocks_pending", len(s.blocks), + "channel_full", s.pendingChannel.IsFull(), + "input_bytes", s.pendingChannel.InputBytes(), + "ready_bytes", s.pendingChannel.ReadyBytes(), + ) + return nil +} + +func (s *channelManager) outputFrames() error { + if err := s.pendingChannel.OutputFrames(); err != nil { + return fmt.Errorf("creating frames with channel builder: %w", err) + } + if !s.pendingChannel.IsFull() { + return nil + } + + inBytes, outBytes := s.pendingChannel.InputBytes(), s.pendingChannel.OutputBytes() + s.metr.RecordChannelClosed( + s.pendingChannel.ID(), + len(s.blocks), + s.pendingChannel.NumFrames(), + inBytes, + outBytes, + s.pendingChannel.FullErr(), + ) + + var comprRatio float64 + if inBytes > 0 { + comprRatio = float64(outBytes) / float64(inBytes) + } + s.log.Info("Channel closed", + "id", s.pendingChannel.ID(), + "blocks_pending", len(s.blocks), + "num_frames", s.pendingChannel.NumFrames(), + "input_bytes", inBytes, + "output_bytes", outBytes, + "full_reason", s.pendingChannel.FullErr(), + "compr_ratio", comprRatio, + ) return nil } @@ -273,3 +333,14 @@ func (s *channelManager) AddL2Block(block *types.Block) error { return nil } + +func l2BlockRefFromBlockAndL1Info(block *types.Block, l1info derive.L1BlockInfo) eth.L2BlockRef { + return eth.L2BlockRef{ + Hash: block.Hash(), + Number: block.NumberU64(), + ParentHash: block.ParentHash(), + Time: block.Time(), + L1Origin: eth.BlockID{Hash: l1info.BlockHash, Number: l1info.Number}, + SequenceNumber: l1info.SequenceNumber, + } +} diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 21d501d60..ba2154937 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" derivetest "github.com/ethereum-optimism/optimism/op-node/rollup/derive/test" @@ -23,7 +24,7 @@ import ( func TestPendingChannelTimeout(t *testing.T) { // Create a new channel manager with a ChannelTimeout log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{ + m := NewChannelManager(log, metrics.NoopMetrics, ChannelConfig{ ChannelTimeout: 100, }) @@ -67,7 +68,7 @@ func TestPendingChannelTimeout(t *testing.T) { // detects a reorg when it has cached L1 blocks. func TestChannelManagerReturnsErrReorg(t *testing.T) { log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{}) + m := NewChannelManager(log, metrics.NoopMetrics, ChannelConfig{}) a := types.NewBlock(&types.Header{ Number: big.NewInt(0), @@ -101,11 +102,12 @@ func TestChannelManagerReturnsErrReorg(t *testing.T) { // detects a reorg even if it does not have any blocks inside it. func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) { log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{ - TargetFrameSize: 0, - MaxFrameSize: 120_000, - ApproxComprRatio: 1.0, - }) + m := NewChannelManager(log, metrics.NoopMetrics, + ChannelConfig{ + TargetFrameSize: 0, + MaxFrameSize: 120_000, + ApproxComprRatio: 1.0, + }) l1Block := types.NewBlock(&types.Header{ BaseFee: big.NewInt(10), Difficulty: common.Big0, @@ -138,7 +140,7 @@ func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) { // TestChannelManagerNextTxData checks the nextTxData function. func TestChannelManagerNextTxData(t *testing.T) { log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{}) + m := NewChannelManager(log, metrics.NoopMetrics, ChannelConfig{}) // Nil pending channel should return EOF returnedTxData, err := m.nextTxData() @@ -181,7 +183,7 @@ func TestClearChannelManager(t *testing.T) { // Create a channel manager log := testlog.Logger(t, log.LvlCrit) rng := rand.New(rand.NewSource(time.Now().UnixNano())) - m := NewChannelManager(log, ChannelConfig{ + m := NewChannelManager(log, metrics.NoopMetrics, ChannelConfig{ // Need to set the channel timeout here so we don't clear pending // channels on confirmation. This would result in [TxConfirmed] // clearing confirmed transactions, and reseting the pendingChannels map @@ -254,7 +256,7 @@ func TestClearChannelManager(t *testing.T) { func TestChannelManagerTxConfirmed(t *testing.T) { // Create a channel manager log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{ + m := NewChannelManager(log, metrics.NoopMetrics, ChannelConfig{ // Need to set the channel timeout here so we don't clear pending // channels on confirmation. This would result in [TxConfirmed] // clearing confirmed transactions, and reseting the pendingChannels map @@ -308,7 +310,7 @@ func TestChannelManagerTxConfirmed(t *testing.T) { func TestChannelManagerTxFailed(t *testing.T) { // Create a channel manager log := testlog.Logger(t, log.LvlCrit) - m := NewChannelManager(log, ChannelConfig{}) + m := NewChannelManager(log, metrics.NoopMetrics, ChannelConfig{}) // Let's add a valid pending transaction to the channel // manager so we can demonstrate correctness @@ -351,11 +353,12 @@ func TestChannelManager_TxResend(t *testing.T) { require := require.New(t) rng := rand.New(rand.NewSource(time.Now().UnixNano())) log := testlog.Logger(t, log.LvlError) - m := NewChannelManager(log, ChannelConfig{ - TargetFrameSize: 0, - MaxFrameSize: 120_000, - ApproxComprRatio: 1.0, - }) + m := NewChannelManager(log, metrics.NoopMetrics, + ChannelConfig{ + TargetFrameSize: 0, + MaxFrameSize: 120_000, + ApproxComprRatio: 1.0, + }) a, _ := derivetest.RandomL2Block(rng, 4) diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index 7c0b9f9b9..1f26b4bdf 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -9,6 +9,7 @@ import ( "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/rpc" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/sources" @@ -20,18 +21,21 @@ import ( ) type Config struct { - log log.Logger - L1Client *ethclient.Client - L2Client *ethclient.Client - RollupNode *sources.RollupClient - PollInterval time.Duration + log log.Logger + metr metrics.Metricer + L1Client *ethclient.Client + L2Client *ethclient.Client + RollupNode *sources.RollupClient + + PollInterval time.Duration + From common.Address + TxManagerConfig txmgr.Config - From common.Address // RollupConfig is queried at startup Rollup *rollup.Config - // Channel creation parameters + // Channel builder parameters Channel ChannelConfig } diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index e288f60fe..4e7d8f578 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -10,7 +10,9 @@ import ( "sync" "time" + "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/eth" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/core/types" @@ -34,13 +36,14 @@ type BatchSubmitter struct { // lastStoredBlock is the last block loaded into `state`. If it is empty it should be set to the l2 safe head. lastStoredBlock eth.BlockID + lastL1Tip eth.L1BlockRef state *channelManager } // NewBatchSubmitterFromCLIConfig initializes the BatchSubmitter, gathering any resources // that will be needed during operation. -func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitter, error) { +func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*BatchSubmitter, error) { ctx := context.Background() signer, fromAddress, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, cfg.SequencerHDPath, cfg.SignerConfig) @@ -104,12 +107,12 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitte return nil, err } - return NewBatchSubmitter(ctx, batcherCfg, l) + return NewBatchSubmitter(ctx, batcherCfg, l, m) } // NewBatchSubmitter initializes the BatchSubmitter, gathering any resources // that will be needed during operation. -func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger) (*BatchSubmitter, error) { +func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger, m metrics.Metricer) (*BatchSubmitter, error) { balance, err := cfg.L1Client.BalanceAt(ctx, cfg.From, nil) if err != nil { return nil, err @@ -118,12 +121,14 @@ func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger) (*BatchSub cfg.log = l cfg.log.Info("creating batch submitter", "submitter_addr", cfg.From, "submitter_bal", balance) + cfg.metr = m + return &BatchSubmitter{ Config: cfg, txMgr: NewTransactionManager(l, cfg.TxManagerConfig, cfg.Rollup.BatchInboxAddress, cfg.Rollup.L1ChainID, cfg.From, cfg.L1Client), - state: NewChannelManager(l, cfg.Channel), + state: NewChannelManager(l, m, cfg.Channel), }, nil } @@ -187,13 +192,16 @@ func (l *BatchSubmitter) Stop() error { func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) { start, end, err := l.calculateL2BlockRangeToStore(ctx) if err != nil { - l.log.Trace("was not able to calculate L2 block range", "err", err) + l.log.Warn("Error calculating L2 block range", "err", err) + return + } else if start.Number == end.Number { return } + var latestBlock *types.Block // Add all blocks to "state" for i := start.Number + 1; i < end.Number+1; i++ { - id, err := l.loadBlockIntoState(ctx, i) + block, err := l.loadBlockIntoState(ctx, i) if errors.Is(err, ErrReorg) { l.log.Warn("Found L2 reorg", "block_number", i) l.state.Clear() @@ -203,24 +211,34 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) { l.log.Warn("failed to load block into state", "err", err) return } - l.lastStoredBlock = id + l.lastStoredBlock = eth.ToBlockID(block) + latestBlock = block } + + l2ref, err := derive.L2BlockToBlockRef(latestBlock, &l.Rollup.Genesis) + if err != nil { + l.log.Warn("Invalid L2 block loaded into state", "err", err) + return + } + + l.metr.RecordL2BlocksLoaded(l2ref) } // loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded. -func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (eth.BlockID, error) { +func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (*types.Block, error) { ctx, cancel := context.WithTimeout(ctx, networkTimeout) + defer cancel() block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) - cancel() if err != nil { - return eth.BlockID{}, err + return nil, fmt.Errorf("getting L2 block: %w", err) } + if err := l.state.AddL2Block(block); err != nil { - return eth.BlockID{}, err + return nil, fmt.Errorf("adding L2 block to state: %w", err) } - id := eth.ToBlockID(block) - l.log.Info("added L2 block to local state", "block", id, "tx_count", len(block.Transactions()), "time", block.Time()) - return id, nil + + l.log.Info("added L2 block to local state", "block", eth.ToBlockID(block), "tx_count", len(block.Transactions()), "time", block.Time()) + return block, nil } // calculateL2BlockRangeToStore determines the range (start,end] that should be loaded into the local state. @@ -283,6 +301,7 @@ func (l *BatchSubmitter) loop() { l.log.Error("Failed to query L1 tip", "error", err) break } + l.recordL1Tip(l1tip) // Collect next transaction data txdata, err := l.state.TxData(l1tip.ID()) @@ -316,6 +335,14 @@ func (l *BatchSubmitter) loop() { } } +func (l *BatchSubmitter) recordL1Tip(l1tip eth.L1BlockRef) { + if l.lastL1Tip == l1tip { + return + } + l.lastL1Tip = l1tip + l.metr.RecordLatestL1Block(l1tip) +} + func (l *BatchSubmitter) recordFailedTx(id txID, err error) { l.log.Warn("Failed to send transaction", "err", err) l.state.TxFailed(id) diff --git a/op-e2e/migration_test.go b/op-e2e/migration_test.go index 1d646a580..69bf616e6 100644 --- a/op-e2e/migration_test.go +++ b/op-e2e/migration_test.go @@ -12,6 +12,7 @@ import ( "time" bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + batchermetrics "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/sources" l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer" @@ -341,7 +342,7 @@ func TestMigration(t *testing.T) { Format: "text", }, PrivateKey: hexPriv(secrets.Batcher), - }, lgr.New("module", "batcher")) + }, lgr.New("module", "batcher"), batchermetrics.NoopMetrics) require.NoError(t, err) t.Cleanup(func() { batcher.StopIfRunning() diff --git a/op-e2e/setup.go b/op-e2e/setup.go index a33b9563c..fea710838 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + batchermetrics "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" @@ -600,7 +601,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { Format: "text", }, PrivateKey: hexPriv(cfg.Secrets.Batcher), - }, sys.cfg.Loggers["batcher"]) + }, sys.cfg.Loggers["batcher"], batchermetrics.NoopMetrics) if err != nil { return nil, fmt.Errorf("failed to setup batch submitter: %w", err) } From 1a9e9475e99e36d8490a92fa6046963652d5fc6f Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Fri, 17 Mar 2023 14:42:48 +0100 Subject: [PATCH 087/404] op-batcher: Improve unit tests By removing some code duplication and a more efficient implementation of the MaxRLPBytes test. --- op-batcher/batcher/channel_builder_test.go | 106 ++++++++++++--------- op-batcher/batcher/channel_manager_test.go | 20 +--- 2 files changed, 62 insertions(+), 64 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 1ea702232..e895f53da 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -2,10 +2,11 @@ package batcher import ( "bytes" - "crypto/rand" "math" "math/big" + "math/rand" "testing" + "time" "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup" @@ -52,54 +53,64 @@ func TestConfigValidation(t *testing.T) { require.ErrorIs(t, validChannelConfig.Check(), ErrInvalidChannelTimeout) } -// addNonsenseBlock is a helper function that adds a nonsense block -// to the channel builder using the [channelBuilder.AddBlock] method. -func addNonsenseBlock(cb *channelBuilder) error { - lBlock := types.NewBlock(&types.Header{ +// addMiniBlock adds a minimal valid L2 block to the channel builder using the +// channelBuilder.AddBlock method. +func addMiniBlock(cb *channelBuilder) error { + a := newMiniL2Block(0) + _, err := cb.AddBlock(a) + return err +} + +// newMiniL2Block returns a minimal L2 block with a minimal valid L1InfoDeposit +// transaction as first transaction. Both blocks are minimal in the sense that +// most fields are left at defaults or are unset. +// +// If numTx > 0, that many empty DynamicFeeTxs will be added to the txs. +func newMiniL2Block(numTx int) *types.Block { + return newMiniL2BlockWithNumberParent(numTx, new(big.Int), (common.Hash{})) +} + +// newMiniL2Block returns a minimal L2 block with a minimal valid L1InfoDeposit +// transaction as first transaction. Both blocks are minimal in the sense that +// most fields are left at defaults or are unset. Block number and parent hash +// will be set to the given parameters number and parent. +// +// If numTx > 0, that many empty DynamicFeeTxs will be added to the txs. +func newMiniL2BlockWithNumberParent(numTx int, number *big.Int, parent common.Hash) *types.Block { + l1Block := types.NewBlock(&types.Header{ BaseFee: big.NewInt(10), Difficulty: common.Big0, Number: big.NewInt(100), }, nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) + l1InfoTx, err := derive.L1InfoDeposit(0, l1Block, eth.SystemConfig{}, false) if err != nil { - return err + panic(err) } - txs := []*types.Transaction{types.NewTx(l1InfoTx)} - a := types.NewBlock(&types.Header{ - Number: big.NewInt(0), + + txs := make([]*types.Transaction, 0, 1+numTx) + txs = append(txs, types.NewTx(l1InfoTx)) + for i := 0; i < numTx; i++ { + txs = append(txs, types.NewTx(&types.DynamicFeeTx{})) + } + + return types.NewBlock(&types.Header{ + Number: number, + ParentHash: parent, }, txs, nil, nil, trie.NewStackTrie(nil)) - _, err = cb.AddBlock(a) - return err } -// buildTooLargeRlpEncodedBlockBatch is a helper function that builds a batch -// of blocks that are too large to be added to a channel. -func buildTooLargeRlpEncodedBlockBatch(cb *channelBuilder) error { - // Construct a block with way too many txs - lBlock := types.NewBlock(&types.Header{ - BaseFee: big.NewInt(10), - Difficulty: common.Big0, - Number: big.NewInt(100), - }, nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, _ := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false) - txs := []*types.Transaction{types.NewTx(l1InfoTx)} - for i := 0; i < 500_000; i++ { - txData := make([]byte, 32) - _, _ = rand.Read(txData) - tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), txData) - txs = append(txs, tx) +// addTooManyBlocks adds blocks to the channel until it hits an error, +// which is presumably ErrTooManyRLPBytes. +func addTooManyBlocks(cb *channelBuilder) error { + for i := 0; i < 10_000; i++ { + block := newMiniL2Block(100) + _, err := cb.AddBlock(block) + if err != nil { + return err + } } - block := types.NewBlock(&types.Header{ - Number: big.NewInt(0), - }, txs, nil, nil, trie.NewStackTrie(nil)) - // Try to add the block to the channel builder - // This should fail since the block is too large - // When a batch is constructed from the block and - // then rlp encoded in the channel out, the size - // will exceed [derive.MaxRLPBytesPerChannel] - _, err := cb.AddBlock(block) - return err + return nil } // FuzzDurationTimeoutZeroMaxChannelDuration ensures that when whenever the MaxChannelDuration @@ -391,7 +402,7 @@ func TestOutputFrames(t *testing.T) { require.Equal(t, 0, readyBytes) // Let's add a block - err = addNonsenseBlock(cb) + err = addMiniBlock(cb) require.NoError(t, err) // Check how many ready bytes @@ -421,17 +432,18 @@ func TestOutputFrames(t *testing.T) { // TestMaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames] // function errors when the max RLP bytes per channel is reached. func TestMaxRLPBytesPerChannel(t *testing.T) { + t.Parallel() channelConfig := defaultTestChannelConfig - channelConfig.MaxFrameSize = 2 + channelConfig.MaxFrameSize = derive.MaxRLPBytesPerChannel * 2 + channelConfig.TargetFrameSize = derive.MaxRLPBytesPerChannel * 2 + channelConfig.ApproxComprRatio = 1 // Construct the channel builder cb, err := newChannelBuilder(channelConfig) require.NoError(t, err) - require.False(t, cb.IsFull()) - require.Equal(t, 0, cb.NumFrames()) // Add a block that overflows the [ChannelOut] - err = buildTooLargeRlpEncodedBlockBatch(cb) + err = addTooManyBlocks(cb) require.ErrorIs(t, err, derive.ErrTooManyRLPBytes) } @@ -494,7 +506,7 @@ func TestBuilderAddBlock(t *testing.T) { require.NoError(t, err) // Add a nonsense block to the channel builder - err = addNonsenseBlock(cb) + err = addMiniBlock(cb) require.NoError(t, err) // Check the fields reset in the AddBlock function @@ -505,7 +517,7 @@ func TestBuilderAddBlock(t *testing.T) { // Since the channel output is full, the next call to AddBlock // should return the channel out full error - err = addNonsenseBlock(cb) + err = addMiniBlock(cb) require.ErrorIs(t, err, ErrInputTargetReached) } @@ -520,7 +532,7 @@ func TestBuilderReset(t *testing.T) { require.NoError(t, err) // Add a nonsense block to the channel builder - err = addNonsenseBlock(cb) + err = addMiniBlock(cb) require.NoError(t, err) // Check the fields reset in the Reset function @@ -536,7 +548,7 @@ func TestBuilderReset(t *testing.T) { require.NoError(t, err) // Add another block to increment the block count - err = addNonsenseBlock(cb) + err = addMiniBlock(cb) require.NoError(t, err) // Check the fields reset in the Reset function diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index ba2154937..335859ca5 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/trie" "github.com/stretchr/testify/require" ) @@ -108,24 +107,11 @@ func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) { MaxFrameSize: 120_000, ApproxComprRatio: 1.0, }) - l1Block := types.NewBlock(&types.Header{ - BaseFee: big.NewInt(10), - Difficulty: common.Big0, - Number: big.NewInt(100), - }, nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, err := derive.L1InfoDeposit(0, l1Block, eth.SystemConfig{}, false) - require.NoError(t, err) - txs := []*types.Transaction{types.NewTx(l1InfoTx)} - a := types.NewBlock(&types.Header{ - Number: big.NewInt(0), - }, txs, nil, nil, trie.NewStackTrie(nil)) - x := types.NewBlock(&types.Header{ - Number: big.NewInt(1), - ParentHash: common.Hash{0xff}, - }, txs, nil, nil, trie.NewStackTrie(nil)) + a := newMiniL2Block(0) + x := newMiniL2BlockWithNumberParent(0, big.NewInt(1), common.Hash{0xff}) - err = m.AddL2Block(a) + err := m.AddL2Block(a) require.NoError(t, err) _, err = m.TxData(eth.BlockID{}) From 4c4bdc3b7db2a8f7e40881b70ede3df47b1e6072 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Fri, 17 Mar 2023 14:43:51 +0100 Subject: [PATCH 088/404] op-batcher: Test channelBuilder.InputBytes --- op-batcher/batcher/channel_builder.go | 3 ++ op-batcher/batcher/channel_builder_test.go | 35 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index b5c5b1d3b..77d7c0d85 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -196,6 +196,9 @@ func (c *channelBuilder) Reset() error { // AddBlock returns a ChannelFullError if called even though the channel is // already full. See description of FullErr for details. // +// AddBlock also returns the L1BlockInfo that got extracted from the block's +// first transaction for subsequent use by the caller. +// // Call OutputFrames() afterwards to create frames. func (c *channelBuilder) AddBlock(block *types.Block) (derive.L1BlockInfo, error) { if c.IsFull() { diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index e895f53da..c582f912c 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -634,3 +634,38 @@ func TestFramePublished(t *testing.T) { // Now the timeout will be 1000 require.Equal(t, uint64(1000), cb.timeout) } + +func TestChannelBuilder_InputBytes(t *testing.T) { + require := require.New(t) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + cb, _ := defaultChannelBuilderSetup(t) + + require.Zero(cb.InputBytes()) + + var l int + for i := 0; i < 5; i++ { + block := newMiniL2Block(rng.Intn(32)) + l += blockBatchRlpSize(t, block) + + _, err := cb.AddBlock(block) + require.NoError(err) + require.Equal(cb.InputBytes(), l) + } +} + +func defaultChannelBuilderSetup(t *testing.T) (*channelBuilder, ChannelConfig) { + t.Helper() + cfg := defaultTestChannelConfig + cb, err := newChannelBuilder(cfg) + require.NoError(t, err, "newChannelBuilder") + return cb, cfg +} + +func blockBatchRlpSize(t *testing.T, b *types.Block) int { + t.Helper() + batch, _, err := derive.BlockToBatch(b) + require.NoError(t, err) + var buf bytes.Buffer + require.NoError(t, batch.EncodeRLP(&buf), "RLP-encoding batch") + return buf.Len() +} From 62003a904fcf0f55796827bbae56482a7e95315b Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Fri, 17 Mar 2023 18:41:03 +0100 Subject: [PATCH 089/404] op-batcher: Test channelBuilder.OutputBytes --- op-batcher/batcher/channel_builder_test.go | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index c582f912c..110321478 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -2,6 +2,7 @@ package batcher import ( "bytes" + "errors" "math" "math/big" "math/rand" @@ -11,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + dtest "github.com/ethereum-optimism/optimism/op-node/rollup/derive/test" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -653,6 +655,41 @@ func TestChannelBuilder_InputBytes(t *testing.T) { } } +func TestChannelBuilder_OutputBytes(t *testing.T) { + require := require.New(t) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + cfg := defaultTestChannelConfig + cfg.TargetFrameSize = 1000 + cfg.MaxFrameSize = 1000 + cfg.TargetNumFrames = 16 + cfg.ApproxComprRatio = 1.0 + cb, err := newChannelBuilder(cfg) + require.NoError(err, "newChannelBuilder") + + require.Zero(cb.OutputBytes()) + + for { + block, _ := dtest.RandomL2Block(rng, rng.Intn(32)) + _, err := cb.AddBlock(block) + if errors.Is(err, ErrInputTargetReached) { + break + } + require.NoError(err) + } + + require.NoError(cb.OutputFrames()) + require.True(cb.IsFull()) + require.Greater(cb.NumFrames(), 1) + + var flen int + for cb.HasFrame() { + f := cb.NextFrame() + flen += len(f.data) + } + + require.Equal(cb.OutputBytes(), flen) +} + func defaultChannelBuilderSetup(t *testing.T) (*channelBuilder, ChannelConfig) { t.Helper() cfg := defaultTestChannelConfig From 265f1f4033c5a50283ebd3644dedffaa65e58e16 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 19 Mar 2023 18:10:58 -0600 Subject: [PATCH 090/404] op-chain-ops: Check and mutate DB in parallel during OVM_ETH migration Updates the precheck script to check and mutate the DB during the OVM_ETH migration. This saves a significant amount of time during the migration, since we avoid performing a full storage iteration over the OVM_ETH state followed by a single-threaded iteration over the addresses to migrate. Now, both run in parallel which makes the runtime of the OVM_ETH migration bounded by how quickly we can update the trie. --- op-chain-ops/ether/migrate.go | 375 ++++++++++++++++-- .../{precheck_test.go => migrate_test.go} | 195 +++++---- op-chain-ops/ether/precheck.go | 321 --------------- op-chain-ops/genesis/check.go | 4 + op-chain-ops/genesis/db_migration.go | 17 +- 5 files changed, 470 insertions(+), 442 deletions(-) rename op-chain-ops/ether/{precheck_test.go => migrate_test.go} (55%) delete mode 100644 op-chain-ops/ether/precheck.go diff --git a/op-chain-ops/ether/migrate.go b/op-chain-ops/ether/migrate.go index 6c9b4999d..51bba05f4 100644 --- a/op-chain-ops/ether/migrate.go +++ b/op-chain-ops/ether/migrate.go @@ -3,6 +3,10 @@ package ether import ( "fmt" "math/big" + "sync" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/util" @@ -13,6 +17,20 @@ import ( "github.com/ethereum/go-ethereum/log" ) +const ( + // checkJobs is the number of parallel workers to spawn + // when iterating the storage trie. + checkJobs = 64 + + // BalanceSlot is an ordinal used to represent slots corresponding to OVM_ETH + // balances in the state. + BalanceSlot = 1 + + // AllowanceSlot is an ordinal used to represent slots corresponding to OVM_ETH + // allowances in the state. + AllowanceSlot = 2 +) + var ( // OVMETHAddress is the address of the OVM ETH predeploy. OVMETHAddress = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000") @@ -27,73 +45,348 @@ var ( common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000005"): true, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000006"): true, } + + // maxSlot is the maximum possible storage slot. + maxSlot = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + + // sequencerEntrypointAddr is the address of the OVM sequencer entrypoint contract. + sequencerEntrypointAddr = common.HexToAddress("0x4200000000000000000000000000000000000005") ) -type FilteredOVMETHAddresses []common.Address +// accountData is a wrapper struct that contains the balance and address of an account. +// It gets passed via channel to the collector process. +type accountData struct { + balance *big.Int + legacySlot common.Hash + address common.Address +} -func MigrateLegacyETH(db *state.StateDB, addresses FilteredOVMETHAddresses, chainID int, noCheck bool) error { +type DBFactory func() (*state.StateDB, error) + +// MigrateBalances migrates all balances in the LegacyERC20ETH contract into state. It performs checks +// in parallel with mutations in order to reduce overall migration time. +func MigrateBalances(mutableDB *state.StateDB, dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) error { // Chain params to use for integrity checking. params := crossdomain.ParamsByChainID[chainID] if params == nil { return fmt.Errorf("no chain params for %d", chainID) } - // Log the chain params for debugging purposes. - log.Info("Chain params", "chain-id", chainID, "supply-delta", params.ExpectedSupplyDelta) + return doMigration(mutableDB, dbFactory, addresses, allowances, params.ExpectedSupplyDelta, noCheck) +} + +func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, expDiff *big.Int, noCheck bool) error { + // We'll need to maintain a list of all addresses that we've seen along with all of the storage + // slots based on the witness data. + slotsAddrs := make(map[common.Hash]common.Address) + slotsInp := make(map[common.Hash]int) - // Migrate the legacy ETH to ETH. - log.Info("Migrating legacy ETH to ETH", "num-accounts", len(addresses)) - totalMigrated := new(big.Int) - logAccountProgress := util.ProgressLogger(1000, "imported accounts") + // For each known address, compute its balance key and add it to the list of addresses. + // Mint events are instrumented as regular ETH events in the witness data, so we no longer + // need to iterate over mint events during the migration. for _, addr := range addresses { - // Balances are pre-checked not have any balances in state. + sk := CalcOVMETHStorageKey(addr) + slotsAddrs[sk] = addr + slotsInp[sk] = BalanceSlot + } + + // For each known allowance, compute its storage key and add it to the list of addresses. + for _, allowance := range allowances { + sk := CalcAllowanceStorageKey(allowance.From, allowance.To) + slotsAddrs[sk] = allowance.From + slotsInp[sk] = AllowanceSlot + } + + // Add the old SequencerEntrypoint because someone sent it ETH a long time ago and it has a + // balance but none of our instrumentation could easily find it. Special case. + entrySK := CalcOVMETHStorageKey(sequencerEntrypointAddr) + slotsAddrs[entrySK] = sequencerEntrypointAddr + slotsInp[entrySK] = BalanceSlot + + // WaitGroup to wait on each iteration job to finish. + var wg sync.WaitGroup + // Channel to receive storage slot keys and values from each iteration job. + outCh := make(chan accountData) + // Channel to receive errors from each iteration job. + errCh := make(chan error, checkJobs) + // Channel to cancel all iteration jobs as well as the collector. + cancelCh := make(chan struct{}) + + // Define a worker function to iterate over each partition. + worker := func(start, end common.Hash) { + // Decrement the WaitGroup when the function returns. + defer wg.Done() + + db, err := dbFactory() + if err != nil { + log.Crit("cannot get database", "err", err) + } + + // Create a new storage trie. Each trie returned by db.StorageTrie + // is a copy, so this is safe for concurrent use. + st, err := db.StorageTrie(predeploys.LegacyERC20ETHAddr) + if err != nil { + // Should never happen, so explode if it does. + log.Crit("cannot get storage trie for LegacyERC20ETHAddr", "err", err) + } + if st == nil { + // Should never happen, so explode if it does. + log.Crit("nil storage trie for LegacyERC20ETHAddr") + } + + it := trie.NewIterator(st.NodeIterator(start.Bytes())) + + // Below code is largely based on db.ForEachStorage. We can't use that + // because it doesn't allow us to specify a start and end key. + for it.Next() { + select { + case <-cancelCh: + // If one of the workers encounters an error, cancel all of them. + return + default: + break + } + + // Use the raw (i.e., secure hashed) key to check if we've reached + // the end of the partition. Use > rather than >= here to account for + // the fact that the values returned by PartitionKeys are inclusive. + // Duplicate addresses that may be returned by this iteration are + // filtered out in the collector. + if new(big.Int).SetBytes(it.Key).Cmp(end.Big()) > 0 { + return + } + + // Skip if the value is empty. + rawValue := it.Value + if len(rawValue) == 0 { + continue + } + + // Get the preimage. + rawKey := st.GetKey(it.Key) + if rawKey == nil { + // Should never happen, so explode if it does. + log.Crit("cannot get preimage for storage key", "key", it.Key) + } + key := common.BytesToHash(rawKey) + + // Parse the raw value. + _, content, _, err := rlp.Split(rawValue) + if err != nil { + // Should never happen, so explode if it does. + log.Crit("mal-formed data in state: %v", err) + } + + // We can safely ignore specific slots (totalSupply, name, symbol). + if ignoredSlots[key] { + continue + } + + slotType, ok := slotsInp[key] + if !ok { + if noCheck { + log.Error("ignoring unknown storage slot in state", "slot", key.String()) + } else { + errCh <- fmt.Errorf("unknown storage slot in state: %s", key.String()) + return + } + } - // Pull out the OVM ETH balance. - ovmBalance := GetOVMETHBalance(db, addr) + // No accounts should have a balance in state. If they do, bail. + addr, ok := slotsAddrs[key] + if !ok { + log.Crit("could not find address in map - should never happen") + } + bal := db.GetBalance(addr) + if bal.Sign() != 0 { + log.Error( + "account has non-zero balance in state - should never happen", + "addr", addr, + "balance", bal.String(), + ) + if !noCheck { + errCh <- fmt.Errorf("account has non-zero balance in state - should never happen: %s", addr.String()) + return + } + } - // Actually perform the migration by setting the appropriate values in state. - db.SetBalance(addr, ovmBalance) - db.SetState(predeploys.LegacyERC20ETHAddr, CalcOVMETHStorageKey(addr), common.Hash{}) + // Add balances to the total found. + switch slotType { + case BalanceSlot: + // Convert the value to a common.Hash, then send to the channel. + value := common.BytesToHash(content) + outCh <- accountData{ + balance: value.Big(), + legacySlot: key, + address: addr, + } + case AllowanceSlot: + // Allowance slot. + continue + default: + // Should never happen. + if noCheck { + log.Error("unknown slot type", "slot", key, "type", slotType) + } else { + log.Crit("unknown slot type %d, should never happen", slotType) + } + } + } + } + + for i := 0; i < checkJobs; i++ { + wg.Add(1) + + // Partition the keyspace per worker. + start, end := PartitionKeyspace(i, checkJobs) + + // Kick off our worker. + go worker(start, end) + } + + // Make a channel to make sure that the collector process completes. + collectorCloseCh := make(chan struct{}) + + // Keep track of the last error seen. + var lastErr error + + // There are multiple ways that the cancel channel can be closed: + // - if we receive an error from the errCh + // - if the collector process completes + // To prevent panics, we wrap the close in a sync.Once. + var cancelOnce sync.Once + + // Create a map of accounts we've seen so that we can filter out duplicates. + seenAccounts := make(map[common.Address]bool) + + // Keep track of the total migrated supply. + totalFound := new(big.Int) + + // Kick off another background process to collect + // values from the channel and add them to the map. + var count int + progress := util.ProgressLogger(1000, "Migrated OVM_ETH storage slot") + go func() { + defer func() { + collectorCloseCh <- struct{}{} + }() + for { + select { + case account := <-outCh: + progress() + + // Filter out duplicate accounts. See the below note about keyspace iteration for + // why we may have to filter out duplicates. + if seenAccounts[account.address] { + log.Info("skipping duplicate account during iteration", "addr", account.address) + continue + } + + // Accumulate addresses and total supply. + totalFound = new(big.Int).Add(totalFound, account.balance) + + mutableDB.SetBalance(account.address, account.balance) + mutableDB.SetState(predeploys.LegacyERC20ETHAddr, account.legacySlot, common.Hash{}) + count++ + seenAccounts[account.address] = true + case err := <-errCh: + cancelOnce.Do(func() { + lastErr = err + close(cancelCh) + }) + case <-cancelCh: + return + } + } + }() + + // Wait for the workers to finish. + wg.Wait() + // Close the cancel channel to signal the collector process to stop. + cancelOnce.Do(func() { + close(cancelCh) + }) + + // Wait for the collector process to finish. + <-collectorCloseCh + + // If we saw an error, return it. + if lastErr != nil { + return lastErr + } - // Bump the total OVM balance. - totalMigrated = totalMigrated.Add(totalMigrated, ovmBalance) + // Log how many slots were iterated over. + log.Info("Iterated legacy balances", "count", count) - // Log progress. - logAccountProgress() + // Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher + // than the actual migrated amount because self-destructs will remove ETH supply in a way that + // cannot be reflected in the contract. This is fine because self-destructs just mean the L2 is + // actually *overcollateralized* by some tiny amount. + db, err := dbFactory() + if err != nil { + log.Crit("cannot get database", "err", err) } - // Make sure that the total supply delta matches the expected delta. This is equivalent to - // checking that the total migrated is equal to the total found, since we already performed the - // same check against the total found (a = b, b = c => a = c). totalSupply := getOVMETHTotalSupply(db) - delta := new(big.Int).Sub(totalSupply, totalMigrated) - if delta.Cmp(params.ExpectedSupplyDelta) != 0 { - if noCheck { - log.Error( - "supply mismatch", - "migrated", totalMigrated.String(), - "supply", totalSupply.String(), - "delta", delta.String(), - "exp_delta", params.ExpectedSupplyDelta.String(), - ) - } else { - log.Crit( - "supply mismatch", - "migrated", totalMigrated.String(), - "supply", totalSupply.String(), - "delta", delta.String(), - "exp_delta", params.ExpectedSupplyDelta.String(), - ) + delta := new(big.Int).Sub(totalSupply, totalFound) + if delta.Cmp(expDiff) != 0 { + log.Error( + "supply mismatch", + "migrated", totalFound.String(), + "supply", totalSupply.String(), + "delta", delta.String(), + "exp_delta", expDiff.String(), + ) + if !noCheck { + return fmt.Errorf("supply mismatch: %s", delta.String()) } } + // Supply is verified. + log.Info( + "supply verified OK", + "migrated", totalFound.String(), + "supply", totalSupply.String(), + "delta", delta.String(), + "exp_delta", expDiff.String(), + ) + // Set the total supply to 0. We do this because the total supply is necessarily going to be // different than the sum of all balances since we no longer track balances inside the contract // itself. The total supply is going to be weird no matter what, might as well set it to zero // so it's explicitly weird instead of implicitly weird. - db.SetState(predeploys.LegacyERC20ETHAddr, getOVMETHTotalSupplySlot(), common.Hash{}) + mutableDB.SetState(predeploys.LegacyERC20ETHAddr, getOVMETHTotalSupplySlot(), common.Hash{}) log.Info("Set the totalSupply to 0") - // Fin. return nil } + +// PartitionKeyspace divides the key space into partitions by dividing the maximum keyspace +// by count then multiplying by i. This will leave some slots left over, which we handle below. It +// returns the start and end keys for the partition as a common.Hash. Note that the returned range +// of keys is inclusive, i.e., [start, end] NOT [start, end). +func PartitionKeyspace(i int, count int) (common.Hash, common.Hash) { + if i < 0 || count < 0 { + panic("i and count must be greater than 0") + } + + if i > count-1 { + panic("i must be less than count - 1") + } + + // Divide the key space into partitions by dividing the key space by the number + // of jobs. This will leave some slots left over, which we handle below. + partSize := new(big.Int).Div(maxSlot.Big(), big.NewInt(int64(count))) + + start := common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i)), partSize)) + var end common.Hash + if i < count-1 { + // If this is not the last partition, use the next partition's start key as the end. + end = common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i+1)), partSize)) + } else { + // If this is the last partition, use the max slot as the end. + end = maxSlot + } + + return start, end +} diff --git a/op-chain-ops/ether/precheck_test.go b/op-chain-ops/ether/migrate_test.go similarity index 55% rename from op-chain-ops/ether/precheck_test.go rename to op-chain-ops/ether/migrate_test.go index 3d234664b..3cd6d0552 100644 --- a/op-chain-ops/ether/precheck_test.go +++ b/op-chain-ops/ether/migrate_test.go @@ -1,14 +1,12 @@ package ether import ( - "bytes" + "fmt" "math/big" "math/rand" - "os" - "sort" "testing" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum/go-ethereum/common" @@ -18,9 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestPreCheckBalances(t *testing.T) { - log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true))) - +func TestMigrateBalances(t *testing.T) { tests := []struct { name string totalSupply *big.Int @@ -29,7 +25,7 @@ func TestPreCheckBalances(t *testing.T) { stateAllowances map[common.Address]common.Address inputAddresses []common.Address inputAllowances []*crossdomain.Allowance - check func(t *testing.T, addrs FilteredOVMETHAddresses, err error) + check func(t *testing.T, db *state.StateDB, err error) }{ { name: "everything matches", @@ -52,12 +48,11 @@ func TestPreCheckBalances(t *testing.T) { To: common.HexToAddress("0x456"), }, }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.NoError(t, err) - require.EqualValues(t, FilteredOVMETHAddresses{ - common.HexToAddress("0x123"), - common.HexToAddress("0x456"), - }, addrs) + require.EqualValues(t, common.Big1, db.GetBalance(common.HexToAddress("0x123"))) + require.EqualValues(t, common.Big2, db.GetBalance(common.HexToAddress("0x456"))) + require.EqualValues(t, common.Hash{}, db.GetState(predeploys.LegacyERC20ETHAddr, GetOVMETHTotalSupplySlot())) }, }, { @@ -71,11 +66,11 @@ func TestPreCheckBalances(t *testing.T) { common.HexToAddress("0x123"), common.HexToAddress("0x456"), }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.NoError(t, err) - require.EqualValues(t, FilteredOVMETHAddresses{ - common.HexToAddress("0x123"), - }, addrs) + require.EqualValues(t, common.Big1, db.GetBalance(common.HexToAddress("0x123"))) + require.EqualValues(t, common.Big0, db.GetBalance(common.HexToAddress("0x456"))) + require.EqualValues(t, common.Hash{}, db.GetState(predeploys.LegacyERC20ETHAddr, GetOVMETHTotalSupplySlot())) }, }, { @@ -102,11 +97,11 @@ func TestPreCheckBalances(t *testing.T) { To: common.HexToAddress("0x789"), }, }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.NoError(t, err) - require.EqualValues(t, FilteredOVMETHAddresses{ - common.HexToAddress("0x123"), - }, addrs) + require.EqualValues(t, common.Big1, db.GetBalance(common.HexToAddress("0x123"))) + require.EqualValues(t, common.Big0, db.GetBalance(common.HexToAddress("0x456"))) + require.EqualValues(t, common.Hash{}, db.GetState(predeploys.LegacyERC20ETHAddr, GetOVMETHTotalSupplySlot())) }, }, { @@ -120,7 +115,7 @@ func TestPreCheckBalances(t *testing.T) { inputAddresses: []common.Address{ common.HexToAddress("0x123"), }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.Error(t, err) require.ErrorContains(t, err, "unknown storage slot") }, @@ -145,7 +140,7 @@ func TestPreCheckBalances(t *testing.T) { To: common.HexToAddress("0x456"), }, }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.Error(t, err) require.ErrorContains(t, err, "unknown storage slot") }, @@ -162,7 +157,7 @@ func TestPreCheckBalances(t *testing.T) { common.HexToAddress("0x123"), common.HexToAddress("0x456"), }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.Error(t, err) require.ErrorContains(t, err, "supply mismatch") }, @@ -179,35 +174,25 @@ func TestPreCheckBalances(t *testing.T) { common.HexToAddress("0x123"), common.HexToAddress("0x456"), }, - check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) { + check: func(t *testing.T, db *state.StateDB, err error) { require.NoError(t, err) - require.EqualValues(t, FilteredOVMETHAddresses{ - common.HexToAddress("0x123"), - common.HexToAddress("0x456"), - }, addrs) + require.EqualValues(t, common.Big1, db.GetBalance(common.HexToAddress("0x123"))) + require.EqualValues(t, common.Big2, db.GetBalance(common.HexToAddress("0x456"))) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - db := makeLegacyETH(t, tt.totalSupply, tt.stateBalances, tt.stateAllowances) - factory := func() (*state.StateDB, error) { - return db, nil - } - addrs, err := doMigration(factory, tt.inputAddresses, tt.inputAllowances, tt.expDiff, false) - - // Sort the addresses since they come in in a random order. - sort.Slice(addrs, func(i, j int) bool { - return bytes.Compare(addrs[i][:], addrs[j][:]) < 0 - }) - - tt.check(t, addrs, err) + db, factory := makeLegacyETH(t, tt.totalSupply, tt.stateBalances, tt.stateAllowances) + err := doMigration(db, factory, tt.inputAddresses, tt.inputAllowances, tt.expDiff, false) + tt.check(t, db, err) }) } } -func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Address]*big.Int, allowances map[common.Address]common.Address) *state.StateDB { - db, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{ +func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Address]*big.Int, allowances map[common.Address]common.Address) (*state.StateDB, DBFactory) { + memDB := rawdb.NewMemoryDatabase() + db, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(memDB, &trie.Config{ Preimages: true, Cache: 1024, }), nil) @@ -235,34 +220,35 @@ func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Addre err = db.Database().TrieDB().Commit(root, true) require.NoError(t, err) - return db + return db, func() (*state.StateDB, error) { + return state.New(root, state.NewDatabaseWithConfig(memDB, &trie.Config{ + Preimages: true, + Cache: 1024, + }), nil) + } } -// TestPreCheckBalancesRandom tests that the pre-check balances function works +// TestMigrateBalancesRandom tests that the pre-check balances function works // with random addresses. This test makes sure that the partition logic doesn't // miss anything. -func TestPreCheckBalancesRandom(t *testing.T) { - addresses := make([]common.Address, 0) - stateBalances := make(map[common.Address]*big.Int) +func TestMigrateBalancesRandom(t *testing.T) { + for i := 0; i < 100; i++ { + addresses := make([]common.Address, 0) + stateBalances := make(map[common.Address]*big.Int) - allowances := make([]*crossdomain.Allowance, 0) - stateAllowances := make(map[common.Address]common.Address) + allowances := make([]*crossdomain.Allowance, 0) + stateAllowances := make(map[common.Address]common.Address) - totalSupply := big.NewInt(0) + totalSupply := big.NewInt(0) - for i := 0; i < 100; i++ { - for i := 0; i < rand.Intn(1000); i++ { + for j := 0; j < rand.Intn(10000); j++ { addr := randAddr(t) addresses = append(addresses, addr) stateBalances[addr] = big.NewInt(int64(rand.Intn(1_000_000))) totalSupply = new(big.Int).Add(totalSupply, stateBalances[addr]) } - sort.Slice(addresses, func(i, j int) bool { - return bytes.Compare(addresses[i][:], addresses[j][:]) < 0 - }) - - for i := 0; i < rand.Intn(1000); i++ { + for j := 0; j < rand.Intn(1000); j++ { addr := randAddr(t) to := randAddr(t) allowances = append(allowances, &crossdomain.Allowance{ @@ -272,19 +258,94 @@ func TestPreCheckBalancesRandom(t *testing.T) { stateAllowances[addr] = to } - db := makeLegacyETH(t, totalSupply, stateBalances, stateAllowances) - factory := func() (*state.StateDB, error) { - return db, nil - } - - outAddrs, err := doMigration(factory, addresses, allowances, big.NewInt(0), false) + db, factory := makeLegacyETH(t, totalSupply, stateBalances, stateAllowances) + err := doMigration(db, factory, addresses, allowances, big.NewInt(0), false) require.NoError(t, err) - sort.Slice(outAddrs, func(i, j int) bool { - return bytes.Compare(outAddrs[i][:], outAddrs[j][:]) < 0 + for addr, expBal := range stateBalances { + actBal := db.GetBalance(addr) + require.EqualValues(t, expBal, actBal) + } + } +} + +func TestPartitionKeyspace(t *testing.T) { + tests := []struct { + i int + count int + expected [2]common.Hash + }{ + { + i: 0, + count: 1, + expected: [2]common.Hash{ + common.HexToHash("0x00"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + { + i: 0, + count: 2, + expected: [2]common.Hash{ + common.HexToHash("0x00"), + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + { + i: 1, + count: 2, + expected: [2]common.Hash{ + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + { + i: 0, + count: 3, + expected: [2]common.Hash{ + common.HexToHash("0x00"), + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + }, + }, + { + i: 1, + count: 3, + expected: [2]common.Hash{ + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + }, + }, + { + i: 2, + count: 3, + expected: [2]common.Hash{ + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("i %d, count %d", tt.i, tt.count), func(t *testing.T) { + start, end := PartitionKeyspace(tt.i, tt.count) + require.Equal(t, tt.expected[0], start) + require.Equal(t, tt.expected[1], end) }) - require.EqualValues(t, addresses, outAddrs) } + + t.Run("panics on invalid i or count", func(t *testing.T) { + require.Panics(t, func() { + PartitionKeyspace(1, 1) + }) + require.Panics(t, func() { + PartitionKeyspace(-1, 1) + }) + require.Panics(t, func() { + PartitionKeyspace(0, -1) + }) + require.Panics(t, func() { + PartitionKeyspace(-1, -1) + }) + }) } func randAddr(t *testing.T) common.Address { diff --git a/op-chain-ops/ether/precheck.go b/op-chain-ops/ether/precheck.go deleted file mode 100644 index 21c45a9ab..000000000 --- a/op-chain-ops/ether/precheck.go +++ /dev/null @@ -1,321 +0,0 @@ -package ether - -import ( - "fmt" - "math/big" - "sync" - - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" - - "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" - "github.com/ethereum-optimism/optimism/op-chain-ops/util" - - "github.com/ethereum-optimism/optimism/op-bindings/predeploys" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/log" -) - -const ( - // checkJobs is the number of parallel workers to spawn - // when iterating the storage trie. - checkJobs = 64 -) - -// maxSlot is the maximum possible storage slot. -var maxSlot = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - -// accountData is a wrapper struct that contains the balance and address of an account. -// It gets passed via channel to the collector process. -type accountData struct { - balance *big.Int - address common.Address -} - -type DBFactory func() (*state.StateDB, error) - -// PreCheckBalances checks that the given list of addresses and allowances represents all storage -// slots in the LegacyERC20ETH contract. We don't have to filter out extra addresses like we do for -// withdrawals because we'll simply carry the balance of a given address to the new system, if the -// account is extra then it won't have any balance and nothing will happen. -func PreCheckBalances(dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) (FilteredOVMETHAddresses, error) { - // Chain params to use for integrity checking. - params := crossdomain.ParamsByChainID[chainID] - if params == nil { - return nil, fmt.Errorf("no chain params for %d", chainID) - } - - return doMigration(dbFactory, addresses, allowances, params.ExpectedSupplyDelta, noCheck) -} - -func doMigration(dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, expDiff *big.Int, noCheck bool) (FilteredOVMETHAddresses, error) { - // We'll need to maintain a list of all addresses that we've seen along with all of the storage - // slots based on the witness data. - addrs := make([]common.Address, 0) - slotsAddrs := make(map[common.Hash]common.Address) - slotsInp := make(map[common.Hash]int) - - // For each known address, compute its balance key and add it to the list of addresses. - // Mint events are instrumented as regular ETH events in the witness data, so we no longer - // need to iterate over mint events during the migration. - for _, addr := range addresses { - sk := CalcOVMETHStorageKey(addr) - slotsAddrs[sk] = addr - slotsInp[sk] = 1 - } - - // For each known allowance, compute its storage key and add it to the list of addresses. - for _, allowance := range allowances { - sk := CalcAllowanceStorageKey(allowance.From, allowance.To) - slotsAddrs[sk] = allowance.From - slotsInp[sk] = 2 - } - - // Add the old SequencerEntrypoint because someone sent it ETH a long time ago and it has a - // balance but none of our instrumentation could easily find it. Special case. - sequencerEntrypointAddr := common.HexToAddress("0x4200000000000000000000000000000000000005") - entrySK := CalcOVMETHStorageKey(sequencerEntrypointAddr) - slotsAddrs[entrySK] = sequencerEntrypointAddr - slotsInp[entrySK] = 1 - - // WaitGroup to wait on each iteration job to finish. - var wg sync.WaitGroup - // Channel to receive storage slot keys and values from each iteration job. - outCh := make(chan accountData) - // Channel to receive errors from each iteration job. - errCh := make(chan error, checkJobs) - // Channel to cancel all iteration jobs as well as the collector. - cancelCh := make(chan struct{}) - - // Keep track of the total migrated supply. - totalFound := new(big.Int) - - // Divide the key space into partitions by dividing the key space by the number - // of jobs. This will leave some slots left over, which we handle below. - partSize := new(big.Int).Div(maxSlot.Big(), big.NewInt(checkJobs)) - - // Define a worker function to iterate over each partition. - worker := func(start, end common.Hash) { - // Decrement the WaitGroup when the function returns. - defer wg.Done() - - db, err := dbFactory() - if err != nil { - log.Crit("cannot get database", "err", err) - } - - // Create a new storage trie. Each trie returned by db.StorageTrie - // is a copy, so this is safe for concurrent use. - st, err := db.StorageTrie(predeploys.LegacyERC20ETHAddr) - if err != nil { - // Should never happen, so explode if it does. - log.Crit("cannot get storage trie for LegacyERC20ETHAddr", "err", err) - } - if st == nil { - // Should never happen, so explode if it does. - log.Crit("nil storage trie for LegacyERC20ETHAddr") - } - - it := trie.NewIterator(st.NodeIterator(start.Bytes())) - - // Below code is largely based on db.ForEachStorage. We can't use that - // because it doesn't allow us to specify a start and end key. - for it.Next() { - select { - case <-cancelCh: - // If one of the workers encounters an error, cancel all of them. - return - default: - break - } - - // Use the raw (i.e., secure hashed) key to check if we've reached - // the end of the partition. - if new(big.Int).SetBytes(it.Key).Cmp(end.Big()) >= 0 { - return - } - - // Skip if the value is empty. - rawValue := it.Value - if len(rawValue) == 0 { - continue - } - - // Get the preimage. - key := common.BytesToHash(st.GetKey(it.Key)) - - // Parse the raw value. - _, content, _, err := rlp.Split(rawValue) - if err != nil { - // Should never happen, so explode if it does. - log.Crit("mal-formed data in state: %v", err) - } - - // We can safely ignore specific slots (totalSupply, name, symbol). - if ignoredSlots[key] { - continue - } - - slotType, ok := slotsInp[key] - if !ok { - if noCheck { - log.Error("ignoring unknown storage slot in state", "slot", key.String()) - } else { - errCh <- fmt.Errorf("unknown storage slot in state: %s", key.String()) - return - } - } - - // No accounts should have a balance in state. If they do, bail. - addr, ok := slotsAddrs[key] - if !ok { - log.Crit("could not find address in map - should never happen") - } - bal := db.GetBalance(addr) - if bal.Sign() != 0 { - log.Error( - "account has non-zero balance in state - should never happen", - "addr", addr, - "balance", bal.String(), - ) - if !noCheck { - errCh <- fmt.Errorf("account has non-zero balance in state - should never happen: %s", addr.String()) - return - } - } - - // Add balances to the total found. - switch slotType { - case 1: - // Convert the value to a common.Hash, then send to the channel. - value := common.BytesToHash(content) - outCh <- accountData{ - balance: value.Big(), - address: addr, - } - case 2: - // Allowance slot. - continue - default: - // Should never happen. - if noCheck { - log.Error("unknown slot type", "slot", key, "type", slotType) - } else { - log.Crit("unknown slot type %d, should never happen", slotType) - } - } - } - } - - for i := 0; i < checkJobs; i++ { - wg.Add(1) - - // Compute the start and end keys for this partition. - start := common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i)), partSize)) - var end common.Hash - if i < checkJobs-1 { - // If this is not the last partition, use the next partition's start key as the end. - end = common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i+1)), partSize)) - } else { - // If this is the last partition, use the max slot as the end. - end = maxSlot - } - - // Kick off our worker. - go worker(start, end) - } - - // Make a channel to make sure that the collector process completes. - collectorCloseCh := make(chan struct{}) - - // Keep track of the last error seen. - var lastErr error - - // There are multiple ways that the cancel channel can be closed: - // - if we receive an error from the errCh - // - if the collector process completes - // To prevent panics, we wrap the close in a sync.Once. - var cancelOnce sync.Once - - // Kick off another background process to collect - // values from the channel and add them to the map. - var count int - progress := util.ProgressLogger(1000, "Collected OVM_ETH storage slot") - go func() { - defer func() { - collectorCloseCh <- struct{}{} - }() - for { - select { - case account := <-outCh: - progress() - // Accumulate addresses and total supply. - addrs = append(addrs, account.address) - totalFound = new(big.Int).Add(totalFound, account.balance) - case err := <-errCh: - lastErr = err - cancelOnce.Do(func() { - close(cancelCh) - }) - case <-cancelCh: - return - } - } - }() - - // Wait for the workers to finish. - wg.Wait() - // Close the cancel channel to signal the collector process to stop. - cancelOnce.Do(func() { - close(cancelCh) - }) - - // Wait for the collector process to finish. - <-collectorCloseCh - - // If we saw an error, return it. - if lastErr != nil { - return nil, lastErr - } - - // Log how many slots were iterated over. - log.Info("Iterated legacy balances", "count", count) - - // Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher - // than the actual migrated amount because self-destructs will remove ETH supply in a way that - // cannot be reflected in the contract. This is fine because self-destructs just mean the L2 is - // actually *overcollateralized* by some tiny amount. - db, err := dbFactory() - if err != nil { - log.Crit("cannot get database", "err", err) - } - - totalSupply := getOVMETHTotalSupply(db) - delta := new(big.Int).Sub(totalSupply, totalFound) - if delta.Cmp(expDiff) != 0 { - log.Error( - "supply mismatch", - "migrated", totalFound.String(), - "supply", totalSupply.String(), - "delta", delta.String(), - "exp_delta", expDiff.String(), - ) - if !noCheck { - return nil, fmt.Errorf("supply mismatch: %s", delta.String()) - } - } - - // Supply is verified. - log.Info( - "supply verified OK", - "migrated", totalFound.String(), - "supply", totalSupply.String(), - "delta", delta.String(), - "exp_delta", expDiff.String(), - ) - - // We know we have at least a superset of all addresses here since we know that we have every - // storage slot. It's fine to have extras because they won't have any balance. - return addrs, nil -} diff --git a/op-chain-ops/genesis/check.go b/op-chain-ops/genesis/check.go index abe569970..c577418a9 100644 --- a/op-chain-ops/genesis/check.go +++ b/op-chain-ops/genesis/check.go @@ -7,6 +7,8 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-chain-ops/util" + "github.com/ethereum-optimism/optimism/op-chain-ops/ether" "github.com/ethereum/go-ethereum/common" @@ -508,7 +510,9 @@ func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1Cros // Now, iterate over each legacy withdrawal and check if there is a corresponding // migrated withdrawal. var innerErr error + progress := util.ProgressLogger(1000, "checking withdrawals") err = db.ForEachStorage(predeploys.LegacyMessagePasserAddr, func(key, value common.Hash) bool { + progress() // The legacy message passer becomes a proxy during the migration, // so we need to ignore the implementation/admin slots. if key == ImplementationSlot || key == AdminSlot { diff --git a/op-chain-ops/genesis/db_migration.go b/op-chain-ops/genesis/db_migration.go index 1eafd1598..f91ce7977 100644 --- a/op-chain-ops/genesis/db_migration.go +++ b/op-chain-ops/genesis/db_migration.go @@ -143,16 +143,6 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m filteredWithdrawals = crossdomain.SafeFilteredWithdrawals(unfilteredWithdrawals) } - // We also need to verify that we have all of the storage slots for the LegacyERC20ETH contract - // that we expect to have. An error will be thrown if there are any missing storage slots. - // Unlike with withdrawals, we do not need to filter out extra addresses because their balances - // would necessarily be zero and therefore not affect the migration. - log.Info("Checking addresses...", "no-check", noCheck) - addrs, err := ether.PreCheckBalances(dbFactory, migrationData.Addresses(), migrationData.OvmAllowances, int(config.L1ChainID), noCheck) - if err != nil { - return nil, fmt.Errorf("addresses mismatch: %w", err) - } - // At this point we've fully verified the witness data for the migration, so we can begin the // actual migration process. This involves modifying parts of the legacy database and inserting // a transition block. @@ -202,11 +192,12 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m } // Finally we migrate the balances held inside the LegacyERC20ETH contract into the state trie. - // We also delete the balances from the LegacyERC20ETH contract. + // We also delete the balances from the LegacyERC20ETH contract. Unlike the steps above, this step + // combines the check and mutation steps into one in order to reduce migration time. log.Info("Starting to migrate ERC20 ETH") - err = ether.MigrateLegacyETH(db, addrs, int(config.L1ChainID), noCheck) + err = ether.MigrateBalances(db, dbFactory, migrationData.Addresses(), migrationData.OvmAllowances, int(config.L1ChainID), noCheck) if err != nil { - return nil, fmt.Errorf("cannot migrate legacy eth: %w", err) + return nil, fmt.Errorf("failed to migrate OVM_ETH: %w", err) } // We're done messing around with the database, so we can now commit the changes to the DB. From 638fa9cdccbb072f926e22276f9931cd24a62e31 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 19 Mar 2023 21:16:34 -0600 Subject: [PATCH 091/404] op-chain-ops: Improve OVM_ETH post check Improves OVM_ETH post-checks by sampling balances and making sure that they appear in state after the migration. Previously, the OVM_ETH post-check only verified that the total supply was set to zero. Will test this on a fresh L2 migration. Depends on #5198 --- op-chain-ops/genesis/check.go | 111 +++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/op-chain-ops/genesis/check.go b/op-chain-ops/genesis/check.go index c577418a9..ca8105052 100644 --- a/op-chain-ops/genesis/check.go +++ b/op-chain-ops/genesis/check.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math/big" + "math/rand" "github.com/ethereum-optimism/optimism/op-chain-ops/util" @@ -26,11 +27,21 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" ) -// MaxSlotChecks is the maximum number of storage slots to check -// when validating the untouched predeploys. This limit is in place -// to bound execution time of the migration. We can parallelize this -// in the future. -const MaxSlotChecks = 1000 +const ( + // MaxPredeploySlotChecks is the maximum number of storage slots to check + // when validating the untouched predeploys. This limit is in place + // to bound execution time of the migration. We can parallelize this + // in the future. + MaxPredeploySlotChecks = 1000 + + // MaxOVMETHSlotChecks is the maximum number of OVM ETH storage slots to check + // when validating the OVM ETH migration. + MaxOVMETHSlotChecks = 5000 + + // OVMETHSampleLikelihood is the probability that a storage slot will be checked + // when validating the OVM ETH migration. + OVMETHSampleLikelihood = 0.1 +) type StorageCheckMap = map[common.Hash]common.Hash @@ -148,7 +159,7 @@ func PostCheckMigratedDB( } log.Info("checked L1Block") - if err := PostCheckLegacyETH(db); err != nil { + if err := PostCheckLegacyETH(prevDB, db, migrationData); err != nil { return err } log.Info("checked legacy eth") @@ -210,7 +221,7 @@ func PostCheckUntouchables(udb state.Database, currDB *state.StateDB, prevRoot c if err := prevDB.ForEachStorage(addr, func(key, value common.Hash) bool { count++ expSlots[key] = value - return count < MaxSlotChecks + return count < MaxPredeploySlotChecks }); err != nil { return fmt.Errorf("error iterating over storage: %w", err) } @@ -365,14 +376,94 @@ func PostCheckPredeployStorage(db vm.StateDB, finalSystemOwner common.Address, p } // PostCheckLegacyETH checks that the legacy eth migration was successful. -// It currently only checks that the total supply was set to 0. -func PostCheckLegacyETH(db vm.StateDB) error { +// It checks that the total supply was set to 0, and randomly samples storage +// slots pre- and post-migration to ensure that balances were correctly migrated. +func PostCheckLegacyETH(prevDB, migratedDB *state.StateDB, migrationData crossdomain.MigrationData) error { + allowanceSlots := make(map[common.Hash]bool) + addresses := make(map[common.Hash]common.Address) + + log.Info("recomputing witness data") + for _, allowance := range migrationData.OvmAllowances { + key := ether.CalcAllowanceStorageKey(allowance.From, allowance.To) + allowanceSlots[key] = true + } + + for _, addr := range migrationData.Addresses() { + addresses[ether.CalcOVMETHStorageKey(addr)] = addr + } + + log.Info("checking legacy eth fixed storage slots") for slot, expValue := range LegacyETHCheckSlots { - actValue := db.GetState(predeploys.LegacyERC20ETHAddr, slot) + actValue := migratedDB.GetState(predeploys.LegacyERC20ETHAddr, slot) if actValue != expValue { return fmt.Errorf("expected slot %s on %s to be %s, but got %s", slot, predeploys.LegacyERC20ETHAddr, expValue, actValue) } } + + var count int + threshold := 100 - int(100*OVMETHSampleLikelihood) + progress := util.ProgressLogger(100, "checking legacy eth balance slots") + var innerErr error + err := prevDB.ForEachStorage(predeploys.LegacyERC20ETHAddr, func(key, value common.Hash) bool { + val := rand.Intn(100) + + // Randomly sample storage slots. + if val > threshold { + return true + } + + // Ignore fixed slots. + if _, ok := LegacyETHCheckSlots[key]; ok { + return true + } + + // Ignore allowances. + if allowanceSlots[key] { + return true + } + + // Grab the address, and bail if we can't find it. + addr, ok := addresses[key] + if !ok { + innerErr = fmt.Errorf("unknown OVM_ETH storage slot %s", key) + return false + } + + // Pull out the pre-migration OVM ETH balance, and the state balance. + ovmETHBalance := value.Big() + ovmETHStateBalance := prevDB.GetBalance(addr) + // Pre-migration state balance should be zero. + if ovmETHStateBalance.Cmp(common.Big0) != 0 { + innerErr = fmt.Errorf("expected OVM_ETH pre-migration state balance for %s to be 0, but got %s", addr, ovmETHStateBalance) + return false + } + + // Migrated state balance should equal the OVM ETH balance. + migratedStateBalance := migratedDB.GetBalance(addr) + if migratedStateBalance.Cmp(ovmETHBalance) != 0 { + innerErr = fmt.Errorf("expected OVM_ETH post-migration state balance for %s to be %s, but got %s", addr, ovmETHStateBalance, migratedStateBalance) + return false + } + // Migrated OVM ETH balance should be zero, since we wipe the slots. + migratedBalance := migratedDB.GetState(predeploys.LegacyERC20ETHAddr, key) + if migratedBalance.Big().Cmp(common.Big0) != 0 { + innerErr = fmt.Errorf("expected OVM_ETH post-migration ERC20 balance for %s to be 0, but got %s", addr, migratedBalance) + return false + } + + progress() + count++ + + // Stop iterating if we've checked enough slots. + return count < MaxOVMETHSlotChecks + }) + if err != nil { + return fmt.Errorf("error iterating over OVM_ETH storage: %w", err) + } + if innerErr != nil { + return innerErr + } + return nil } From 32f66df89f278d636c0ba14978bfc58ed8997495 Mon Sep 17 00:00:00 2001 From: clabby Date: Mon, 20 Mar 2023 12:13:02 -0400 Subject: [PATCH 092/404] Update comment --- .../contracts/test/L2ERC721Bridge.t.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol b/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol index 1b9332228..fabc6f58e 100644 --- a/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/contracts/test/L2ERC721Bridge.t.sol @@ -377,9 +377,12 @@ contract L2ERC721Bridge_Test is Messenger_Initializer { } } -/// @dev A non-compliant ERC721 token that does not implement the full ERC721 interface. -/// This is used to test that the bridge will revert if the token does not claim to support -/// the ERC721 interface. +/** + * @dev A non-compliant ERC721 token that does not implement the full ERC721 interface. + * + * This is used to test that the bridge will revert if the token does not claim to support + * the ERC721 interface. + */ contract NonCompliantERC721 { address internal immutable owner; From 98fbe9d223e908bd9d1209a83d491a8f0225a4b4 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Mon, 20 Mar 2023 13:19:17 -0400 Subject: [PATCH 093/404] feat(ctb): Add constructor to init the SD implementation --- .changeset/pink-chicken-hear.md | 5 ++++ .../contracts/deployment/SystemDictator.sol | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .changeset/pink-chicken-hear.md diff --git a/.changeset/pink-chicken-hear.md b/.changeset/pink-chicken-hear.md new file mode 100644 index 000000000..052329fe1 --- /dev/null +++ b/.changeset/pink-chicken-hear.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/contracts-bedrock': patch +--- + +Added a contsructor to the System Dictator diff --git a/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol b/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol index 1888175b5..f73632c88 100644 --- a/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol +++ b/packages/contracts-bedrock/contracts/deployment/SystemDictator.sol @@ -155,6 +155,33 @@ contract SystemDictator is OwnableUpgradeable { currentStep++; } + /** + * @notice Constructor required to ensure that the implementation of the SystemDictator is + * initialized upon deployment. + */ + constructor() { + // Using this shorter variable as an alias for address(0) just prevents us from having to + // to use a new line for every single parameter. + address zero = address(0); + initialize( + DeployConfig( + GlobalConfig(AddressManager(zero), ProxyAdmin(zero), zero, zero), + ProxyAddressConfig(zero, zero, zero, zero, zero, zero, zero), + ImplementationAddressConfig( + L2OutputOracle(zero), + OptimismPortal(payable(zero)), + L1CrossDomainMessenger(zero), + L1StandardBridge(payable(zero)), + OptimismMintableERC20Factory(zero), + L1ERC721Bridge(zero), + PortalSender(zero), + SystemConfig(zero) + ), + SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero) + ) + ); + } + /** * @param _config System configuration. */ From a9db57ad6f16b5000c44925a8f26be89f8f80f08 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 15:13:52 -0400 Subject: [PATCH 094/404] fix nits :gear: --- op-batcher/batcher/channel_builder_test.go | 3 ++ op-batcher/batcher/channel_manager_test.go | 62 +++++++++++----------- op-node/rollup/derive/channel_out.go | 6 ++- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index d15b7944c..7fef5346f 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -419,6 +419,9 @@ func TestOutputFramesHappy(t *testing.T) { // There should be many frames in the channel builder now require.Greater(t, cb.NumFrames(), 1) + for _, frame := range cb.frames { + require.Len(t, frame.data, int(channelConfig.MaxFrameSize)) + } } // TestMaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames] diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index b17b3792b..ec04fb1f9 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -156,8 +156,10 @@ func TestChannelManagerNextTxData(t *testing.T) { require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) } -// TestClearChannelManager tests clearing the channel manager. -func TestClearChannelManager(t *testing.T) { +// TestChannelManager_Clear tests clearing the channel manager. +func TestChannelManager_Clear(t *testing.T) { + require := require.New(t) + // Create a channel manager log := testlog.Logger(t, log.LvlCrit) rng := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -174,11 +176,11 @@ func TestClearChannelManager(t *testing.T) { }) // Channel Manager state should be empty by default - require.Empty(t, m.blocks) - require.Equal(t, common.Hash{}, m.tip) - require.Nil(t, m.pendingChannel) - require.Empty(t, m.pendingTransactions) - require.Empty(t, m.confirmedTransactions) + require.Empty(m.blocks) + require.Equal(common.Hash{}, m.tip) + require.Nil(m.pendingChannel) + require.Empty(m.pendingTransactions) + require.Empty(m.confirmedTransactions) // Add a block to the channel manager a, _ := derivetest.RandomL2Block(rng, 4) @@ -187,25 +189,25 @@ func TestClearChannelManager(t *testing.T) { Hash: a.Hash(), Number: a.NumberU64(), } - require.NoError(t, m.AddL2Block(a)) + require.NoError(m.AddL2Block(a)) // Make sure there is a channel builder - require.NoError(t, m.ensurePendingChannel(l1BlockID)) - require.NotNil(t, m.pendingChannel) - require.Equal(t, 0, len(m.confirmedTransactions)) + require.NoError(m.ensurePendingChannel(l1BlockID)) + require.NotNil(m.pendingChannel) + require.Len(m.confirmedTransactions, 0) // Process the blocks // We should have a pending channel with 1 frame // and no more blocks since processBlocks consumes // the list - require.NoError(t, m.processBlocks()) - require.NoError(t, m.pendingChannel.co.Flush()) - require.NoError(t, m.pendingChannel.OutputFrames()) + require.NoError(m.processBlocks()) + require.NoError(m.pendingChannel.co.Flush()) + require.NoError(m.pendingChannel.OutputFrames()) _, err := m.nextTxData() - require.NoError(t, err) - require.Equal(t, 0, len(m.blocks)) - require.Equal(t, newL1Tip, m.tip) - require.Equal(t, 1, len(m.pendingTransactions)) + require.NoError(err) + require.Len(m.blocks, 0) + require.Equal(newL1Tip, m.tip) + require.Len(m.pendingTransactions, 1) // Add a new block so we can test clearing // the channel manager with a full state @@ -213,19 +215,19 @@ func TestClearChannelManager(t *testing.T) { Number: big.NewInt(1), ParentHash: a.Hash(), }, nil, nil, nil, nil) - require.NoError(t, m.AddL2Block(b)) - require.Equal(t, 1, len(m.blocks)) - require.Equal(t, b.Hash(), m.tip) + require.NoError(m.AddL2Block(b)) + require.Len(m.blocks, 1) + require.Equal(b.Hash(), m.tip) // Clear the channel manager m.Clear() // Check that the entire channel manager state cleared - require.Empty(t, m.blocks) - require.Equal(t, common.Hash{}, m.tip) - require.Nil(t, m.pendingChannel) - require.Empty(t, m.pendingTransactions) - require.Empty(t, m.confirmedTransactions) + require.Empty(m.blocks) + require.Equal(common.Hash{}, m.tip) + require.Nil(m.pendingChannel) + require.Empty(m.pendingTransactions) + require.Empty(m.confirmedTransactions) } // TestChannelManagerTxConfirmed checks the [ChannelManager.TxConfirmed] function. @@ -259,7 +261,7 @@ func TestChannelManagerTxConfirmed(t *testing.T) { require.Equal(t, expectedTxData, returnedTxData) require.Equal(t, 0, m.pendingChannel.NumFrames()) require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) - require.Equal(t, 1, len(m.pendingTransactions)) + require.Len(t, m.pendingTransactions, 1) // An unknown pending transaction should not be marked as confirmed // and should not be removed from the pending transactions map @@ -270,14 +272,14 @@ func TestChannelManagerTxConfirmed(t *testing.T) { blockID := eth.BlockID{Number: 0, Hash: common.Hash{0x69}} m.TxConfirmed(unknownTxID, blockID) require.Empty(t, m.confirmedTransactions) - require.Equal(t, 1, len(m.pendingTransactions)) + require.Len(t, m.pendingTransactions, 1) // Now let's mark the pending transaction as confirmed // and check that it is removed from the pending transactions map // and added to the confirmed transactions map m.TxConfirmed(expectedChannelID, blockID) require.Empty(t, m.pendingTransactions) - require.Equal(t, 1, len(m.confirmedTransactions)) + require.Len(t, m.confirmedTransactions, 1) require.Equal(t, blockID, m.confirmedTransactions[expectedChannelID]) } @@ -307,7 +309,7 @@ func TestChannelManagerTxFailed(t *testing.T) { require.Equal(t, expectedTxData, returnedTxData) require.Equal(t, 0, m.pendingChannel.NumFrames()) require.Equal(t, expectedTxData, m.pendingTransactions[expectedChannelID]) - require.Equal(t, 1, len(m.pendingTransactions)) + require.Len(t, m.pendingTransactions, 1) // Trying to mark an unknown pending transaction as failed // shouldn't modify state diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index a5416fd51..90614a81d 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -19,8 +19,10 @@ var ErrNotDepositTx = errors.New("first transaction in block is not a deposit tx var ErrTooManyRLPBytes = errors.New("batch would cause RLP bytes to go over limit") // FrameV0OverHeadSize is the absolute minimum size of a frame. -// This is the fixed overhead frame size, -// calculated as follows: 16 + 2 + 4 + 1 = 23 bytes. +// This is the fixed overhead frame size, calculated as specified +// in the [Frame Format] specs: 16 + 2 + 4 + 1 = 23 bytes. +// +// [Frame Format]: https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#frame-format const FrameV0OverHeadSize = 23 type ChannelOut struct { From 98c199476253e678246f5fc96df9565a785f4a73 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Fri, 17 Mar 2023 12:40:54 -0700 Subject: [PATCH 095/404] op-proposer: add l2 block height metrics --- op-proposer/proposer/l2_output_submitter.go | 29 ++++++++++++--------- op-service/metrics/block.go | 16 ++++++++++++ 2 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 op-service/metrics/block.go diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 72bc25f5a..f60b82ffb 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/prometheus/client_golang/prometheus" "math/big" _ "net/http/pprof" "os" @@ -78,17 +79,16 @@ func Main(version string, cliCtx *cli.Context) error { }() } - registry := opmetrics.NewRegistry() metricsCfg := cfg.MetricsConfig if metricsCfg.Enabled { l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) go func() { - if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { + if err := opmetrics.ListenAndServe(ctx, l2OutputSubmitter.metricsRegistry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { l.Error("error starting metrics server", err) } }() addr := l2OutputSubmitter.from - opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", l2OutputSubmitter.l1Client, addr) + opmetrics.LaunchBalanceMetrics(ctx, l, l2OutputSubmitter.metricsRegistry, "", l2OutputSubmitter.l1Client, addr) } rpcCfg := cfg.RPCConfig @@ -113,10 +113,11 @@ func Main(version string, cliCtx *cli.Context) error { // L2OutputSubmitter is responsible for proposing outputs type L2OutputSubmitter struct { - txMgr txmgr.TxManager - wg sync.WaitGroup - done chan struct{} - log log.Logger + txMgr txmgr.TxManager + wg sync.WaitGroup + done chan struct{} + log log.Logger + metricsRegistry *prometheus.Registry ctx context.Context cancel context.CancelFunc @@ -223,11 +224,12 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) rawL2ooContract := bind.NewBoundContract(cfg.L2OutputOracleAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client) return &L2OutputSubmitter{ - txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), - done: make(chan struct{}), - log: l, - ctx: ctx, - cancel: cancel, + txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), + done: make(chan struct{}), + log: l, + metricsRegistry: opmetrics.NewRegistry(), + ctx: ctx, + cancel: cancel, l1Client: cfg.L1Client, rollupClient: cfg.RollupClient, @@ -375,6 +377,9 @@ func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Trans return err } + // Emit the proposed block Number + opmetrics.EmitBlockNumber(l.metricsRegistry, "", receipt.BlockNumber) + // The transaction was successfully submitted l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash) return nil diff --git a/op-service/metrics/block.go b/op-service/metrics/block.go new file mode 100644 index 000000000..7fca48c5c --- /dev/null +++ b/op-service/metrics/block.go @@ -0,0 +1,16 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus/promauto" + "math/big" + + "github.com/prometheus/client_golang/prometheus" +) + +func EmitBlockNumber(r *prometheus.Registry, ns string, blockNumber *big.Int) { + promauto.With(r).NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "latest_block_number", + Help: "Latest L2 proposed block number", + }).Set(float64(blockNumber.Uint64())) +} From ec1c0357ea0c1bd1cfd77eedc0ea7c50eb6646c8 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Fri, 17 Mar 2023 12:49:02 -0700 Subject: [PATCH 096/404] imports lint --- op-proposer/proposer/l2_output_submitter.go | 3 ++- op-service/metrics/block.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index f60b82ffb..6b03a768f 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/prometheus/client_golang/prometheus" "math/big" _ "net/http/pprof" "os" @@ -14,6 +13,8 @@ import ( "syscall" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" diff --git a/op-service/metrics/block.go b/op-service/metrics/block.go index 7fca48c5c..2fd5efeff 100644 --- a/op-service/metrics/block.go +++ b/op-service/metrics/block.go @@ -1,9 +1,10 @@ package metrics import ( - "github.com/prometheus/client_golang/prometheus/promauto" "math/big" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus" ) From 6a033e054b765ec1f000eadf95375e4957788f35 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Fri, 17 Mar 2023 14:38:13 -0700 Subject: [PATCH 097/404] refactor to make it extensible --- op-proposer/proposer/config.go | 1 + op-proposer/proposer/l2_output_submitter.go | 39 ++++++++++++--------- op-service/metrics/block.go | 17 --------- op-service/metrics/proposer.go | 25 +++++++++++++ 4 files changed, 48 insertions(+), 34 deletions(-) delete mode 100644 op-service/metrics/block.go create mode 100644 op-service/metrics/proposer.go diff --git a/op-proposer/proposer/config.go b/op-proposer/proposer/config.go index 2fbd9c29e..bc851dbcb 100644 --- a/op-proposer/proposer/config.go +++ b/op-proposer/proposer/config.go @@ -30,6 +30,7 @@ type Config struct { AllowNonFinalized bool From common.Address SignerFnFactory opcrypto.SignerFactory + metricsEnabled bool } // CLIConfig is a well typed config that is parsed from the CLI params. diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 6b03a768f..008817b38 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -13,8 +13,6 @@ import ( "syscall" "time" - "github.com/prometheus/client_golang/prometheus" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -80,16 +78,19 @@ func Main(version string, cliCtx *cli.Context) error { }() } + registry := opmetrics.NewRegistry() metricsCfg := cfg.MetricsConfig if metricsCfg.Enabled { + metricsRegistry := opmetrics.InitProposerMetricsRegistry(registry, "") + l2OutputSubmitter.mr = metricsRegistry l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) go func() { - if err := opmetrics.ListenAndServe(ctx, l2OutputSubmitter.metricsRegistry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { + if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { l.Error("error starting metrics server", err) } }() addr := l2OutputSubmitter.from - opmetrics.LaunchBalanceMetrics(ctx, l, l2OutputSubmitter.metricsRegistry, "", l2OutputSubmitter.l1Client, addr) + opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", l2OutputSubmitter.l1Client, addr) } rpcCfg := cfg.RPCConfig @@ -114,11 +115,12 @@ func Main(version string, cliCtx *cli.Context) error { // L2OutputSubmitter is responsible for proposing outputs type L2OutputSubmitter struct { - txMgr txmgr.TxManager - wg sync.WaitGroup - done chan struct{} - log log.Logger - metricsRegistry *prometheus.Registry + txMgr txmgr.TxManager + wg sync.WaitGroup + done chan struct{} + log log.Logger + mr *opmetrics.ProposerMetricsRegistry + metricsEnabled bool ctx context.Context cancel context.CancelFunc @@ -185,6 +187,7 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu AllowNonFinalized: cfg.AllowNonFinalized, From: fromAddress, SignerFnFactory: signer, + metricsEnabled: cfg.MetricsConfig.Enabled, } return NewL2OutputSubmitter(proposerCfg, l) @@ -225,12 +228,12 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) rawL2ooContract := bind.NewBoundContract(cfg.L2OutputOracleAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client) return &L2OutputSubmitter{ - txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), - done: make(chan struct{}), - log: l, - metricsRegistry: opmetrics.NewRegistry(), - ctx: ctx, - cancel: cancel, + txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), + done: make(chan struct{}), + log: l, + ctx: ctx, + cancel: cancel, + metricsEnabled: cfg.metricsEnabled, l1Client: cfg.L1Client, rollupClient: cfg.RollupClient, @@ -378,8 +381,10 @@ func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Trans return err } - // Emit the proposed block Number - opmetrics.EmitBlockNumber(l.metricsRegistry, "", receipt.BlockNumber) + if l.metricsEnabled { + // Emit the proposed block Number + opmetrics.EmitBlockNumber(l.mr.BlockNumberGauge, "", receipt.BlockNumber) + } // The transaction was successfully submitted l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash) diff --git a/op-service/metrics/block.go b/op-service/metrics/block.go deleted file mode 100644 index 2fd5efeff..000000000 --- a/op-service/metrics/block.go +++ /dev/null @@ -1,17 +0,0 @@ -package metrics - -import ( - "math/big" - - "github.com/prometheus/client_golang/prometheus/promauto" - - "github.com/prometheus/client_golang/prometheus" -) - -func EmitBlockNumber(r *prometheus.Registry, ns string, blockNumber *big.Int) { - promauto.With(r).NewGauge(prometheus.GaugeOpts{ - Namespace: ns, - Name: "latest_block_number", - Help: "Latest L2 proposed block number", - }).Set(float64(blockNumber.Uint64())) -} diff --git a/op-service/metrics/proposer.go b/op-service/metrics/proposer.go new file mode 100644 index 000000000..e782462d1 --- /dev/null +++ b/op-service/metrics/proposer.go @@ -0,0 +1,25 @@ +package metrics + +import ( + "math/big" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +type ProposerMetricsRegistry struct { + BlockNumberGauge prometheus.Gauge +} + +func InitProposerMetricsRegistry(r *prometheus.Registry, ns string) *ProposerMetricsRegistry { + blockNumberGauge := promauto.With(r).NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "latest_block_number", + Help: "Latest L2 proposed block number", + }) + + return &ProposerMetricsRegistry{BlockNumberGauge: blockNumberGauge} +} +func EmitBlockNumber(gauge prometheus.Gauge, ns string, blockNumber *big.Int) { + gauge.Set(float64(blockNumber.Uint64())) +} From 9f453e7b435a8024cfe32d78c6d350c185f7e279 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Fri, 17 Mar 2023 14:40:06 -0700 Subject: [PATCH 098/404] remove namespace --- op-proposer/proposer/l2_output_submitter.go | 2 +- op-service/metrics/proposer.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 008817b38..901d0c2e7 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -383,7 +383,7 @@ func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Trans if l.metricsEnabled { // Emit the proposed block Number - opmetrics.EmitBlockNumber(l.mr.BlockNumberGauge, "", receipt.BlockNumber) + opmetrics.EmitBlockNumber(l.mr.BlockNumberGauge, receipt.BlockNumber) } // The transaction was successfully submitted diff --git a/op-service/metrics/proposer.go b/op-service/metrics/proposer.go index e782462d1..cc05304c3 100644 --- a/op-service/metrics/proposer.go +++ b/op-service/metrics/proposer.go @@ -20,6 +20,6 @@ func InitProposerMetricsRegistry(r *prometheus.Registry, ns string) *ProposerMet return &ProposerMetricsRegistry{BlockNumberGauge: blockNumberGauge} } -func EmitBlockNumber(gauge prometheus.Gauge, ns string, blockNumber *big.Int) { +func EmitBlockNumber(gauge prometheus.Gauge, blockNumber *big.Int) { gauge.Set(float64(blockNumber.Uint64())) } From df353ebf0f55a758c2b026b110238bed8c77030f Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Mon, 20 Mar 2023 16:27:29 -0500 Subject: [PATCH 099/404] feat(docs/op-stack): Add the SDK --- docs/op-stack/src/.vuepress/config.js | 1 + docs/op-stack/src/docs/build/sdk.md | 155 ++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 docs/op-stack/src/docs/build/sdk.md diff --git a/docs/op-stack/src/.vuepress/config.js b/docs/op-stack/src/.vuepress/config.js index 2c692dcaf..3d67a2ea9 100644 --- a/docs/op-stack/src/.vuepress/config.js +++ b/docs/op-stack/src/.vuepress/config.js @@ -150,6 +150,7 @@ module.exports = { '/docs/build/getting-started.md', '/docs/build/conf.md', '/docs/build/explorer.md', + '/docs/build/sdk.md', { title: "OP Stack Hacks", collapsable: true, diff --git a/docs/op-stack/src/docs/build/sdk.md b/docs/op-stack/src/docs/build/sdk.md new file mode 100644 index 000000000..37d5a838c --- /dev/null +++ b/docs/op-stack/src/docs/build/sdk.md @@ -0,0 +1,155 @@ +--- +title: Using the SDK with OP Stack +lang: en-US +--- + +When building applications for use with your OP Stack, you can continue to use [the Optimism JavaScript SDK](https://sdk.optimism.io/). +The main difference is you need to provide some contract addresses to the `CrossDomainMessenger` because they aren't preconfigured. + + +## Contract addresses + +### L1 contract addresses + +The contract addresses are in `.../optimism/packages/contracts-bedrock/deployments/getting-started`, which you created when you deployed the L1 contracts. + +| Contract name when creating `CrossDomainMessenger` | File with address | +| - | - | +| `AddressManager` | `Lib_AddressManager.json` +| `L1CrossDomainMessenger` | `Proxy__OVM_L1CrossDomainMessenger.json` +| `L1StandardBridge` | `Proxy__OVM_L1StandardBridge.json` +| `OptimismPortal` | `OptimismPortalProxy.json` +| `L2OutputOracle` | `L2OutputOracleProxy.json` + + +### Unneeded contract addresses + +Some contracts are required by the SDK, but not actually used. +For these contracts you can just specify the zero address: + +- `StateCommitmentChain` +- `CanonicalTransactionChain` +- `BondManager` + +In JavaScript you can create the zero address using the expression `"0x".padEnd(42, "0")`. + +## The CrossChainMessenger object + +These directions assume you are inside the [Hardhat console](https://hardhat.org/hardhat-runner/docs/guides/hardhat-console). +They further assume that your project already includes the Optimism SDK [`@eth-optimism/sdk`](https://www.npmjs.com/package/@eth-optimism/sdk). + +1. Import the SDK + + ```js + optimismSDK = require("@eth-optimism/sdk") + ``` + +1. Set the configuration parameters. + + | Variable name | Value | + | - | - | + | `l1Url` | URL to an RPC provider for L1, for example `https://eth-goerli.g.alchemy.com/v2/` + | `l2Url` | URL to your OP Stack. If running on the same computer, it is `http://localhost:8545` + | `privKey` | The private key for an account that has some ETH on the L1 + + +1. Create the [providers](https://docs.ethers.org/v5/api/providers/) and [signers](https://docs.ethers.org/v5/api/signer/). + + ```js + l1Provider = new ethers.providers.JsonRpcProvider(l1Url) + l2Provider = new ethers.providers.JsonRpcProvider(l2Url) + l1Signer = new ethers.Wallet(privKey).connect(l1Provider) + l2Signer = new ethers.Wallet(privKey).connect(l2Provider) + ``` + +1. Create the L1 contracts structure. + + ```js + zeroAddr = "0x".padEnd(42, "0") + l1Contracts = { + StateCommitmentChain: zeroAddr, + CanonicalTransactionChain: zeroAddr, + BondManager: zeroAddr, + // These contracts have the addresses you found out earlier. + AddressManager: "0x....", // Lib_AddressManager.json + L1CrossDomainMessenger: "0x....", // Proxy__OVM_L1CrossDomainMessenger.json + L1StandardBridge: "0x....", // Proxy__OVM_L1StandardBridge.json + OptimismPortal: "0x....", // OptimismPortalProxy.json + L2OutputOracle: "0x....", // L2OutputOracleProxy.json + } + ``` + +1. Create the data structure for the standard bridge. + + ```js + bridges = { + Standard: { + l1Bridge: l1Contracts.L1StandardBridge, + l2Bridge: "0x4200000000000000000000000000000000000010", + Adapter: optimismSDK.StandardBridgeAdapter + }, + ETH: { + l1Bridge: l1Contracts.L1StandardBridge, + l2Bridge: "0x4200000000000000000000000000000000000010", + Adapter: optimismSDK.ETHBridgeAdapter + } + } + ``` + + +1. Create the [`CrossChainMessenger`](https://sdk.optimism.io/classes/crosschainmessenger) object. + + ```js + crossChainMessenger = new optimismSDK.CrossChainMessenger({ + bedrock: true, + contracts: { + l1: l1Contracts + }, + bridges: bridges, + l1ChainId: await l1Signer.getChainId(), + l2ChainId: await l2Signer.getChainId(), + l1SignerOrProvider: l1Signer, + l2SignerOrProvider: l2Signer, + }) + ``` + +## Verify SDK functionality + +To verify the SDK's functionality, transfer some ETH from L1 to L2. + +1. Get the current balances. + + ```js + balances0 = [ + await l1Provider.getBalance(l1Signer.address), + await l2Provider.getBalance(l1Signer.address) + ] + ``` + +1. Transfer 1 gwei. + + ```js + tx = await crossChainMessenger.depositETH(1e9) + rcpt = await tx.wait() + ``` + +1. Get the balances after the transfer. + + ```js + balances1 = [ + await l1Provider.getBalance(l1Signer.address), + await l2Provider.getBalance(l1Signer.address) + ] + ``` + +1. See that the L1 balance changed (probably by a lot more than 1 gwei because of the cost of the transaction). + + ```js + (balances0[0]-balances1[0])/1e9 + ``` + +1. See that the L2 balance changed (it might take a few minutes). + + ```js + ((await l2Provider.getBalance(l1Signer.address))-balances0[1])/1e9 + ``` From 43519a24cce8617f1259bebf2a7dc5376c607175 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 20 Mar 2023 15:02:54 -0700 Subject: [PATCH 100/404] refactor to use ref metrics --- op-batcher/metrics/metrics.go | 2 +- op-proposer/metrics/metrics.go | 100 ++++++++++++++++++++ op-proposer/metrics/noop.go | 13 +++ op-proposer/proposer/l2_output_submitter.go | 24 +++-- op-service/metrics/proposer.go | 25 ----- 5 files changed, 125 insertions(+), 39 deletions(-) create mode 100644 op-proposer/metrics/metrics.go create mode 100644 op-proposer/metrics/noop.go delete mode 100644 op-service/metrics/proposer.go diff --git a/op-batcher/metrics/metrics.go b/op-batcher/metrics/metrics.go index f7093a6e8..31ba792cb 100644 --- a/op-batcher/metrics/metrics.go +++ b/op-batcher/metrics/metrics.go @@ -185,7 +185,7 @@ func (m *Metrics) RecordLatestL1Block(l1ref eth.L1BlockRef) { m.RecordL1Ref("latest", l1ref) } -// RecordL2BlockLoaded should be called when a new L2 block was loaded into the +// RecordL2BlocksLoaded should be called when a new L2 block was loaded into the // channel manager (but not processed yet). func (m *Metrics) RecordL2BlocksLoaded(l2ref eth.L2BlockRef) { m.RecordL2Ref(StageLoaded, l2ref) diff --git a/op-proposer/metrics/metrics.go b/op-proposer/metrics/metrics.go new file mode 100644 index 000000000..a5296b637 --- /dev/null +++ b/op-proposer/metrics/metrics.go @@ -0,0 +1,100 @@ +package metrics + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-node/eth" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/prometheus/client_golang/prometheus" + + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" +) + +const Namespace = "op_proposer" + +type Metricer interface { + RecordInfo(version string) + RecordUp() + + // Records all L1 and L2 block events + opmetrics.RefMetricer + + RecordL2BlocksProposed(l2ref eth.L2BlockRef) +} + +type Metrics struct { + ns string + registry *prometheus.Registry + factory opmetrics.Factory + + opmetrics.RefMetrics + + Info prometheus.GaugeVec + Up prometheus.Gauge +} + +var _ Metricer = (*Metrics)(nil) + +func NewMetrics(procName string) *Metrics { + if procName == "" { + procName = "default" + } + ns := Namespace + "_" + procName + + registry := opmetrics.NewRegistry() + factory := opmetrics.With(registry) + + return &Metrics{ + ns: ns, + registry: registry, + factory: factory, + + RefMetrics: opmetrics.MakeRefMetrics(ns, factory), + + Info: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "info", + Help: "Pseudo-metric tracking version and config info", + }, []string{ + "version", + }), + Up: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "up", + Help: "1 if the op-proposer has finished starting up", + }), + } +} + +func (m *Metrics) Serve(ctx context.Context, host string, port int) error { + return opmetrics.ListenAndServe(ctx, m.registry, host, port) +} + +func (m *Metrics) StartBalanceMetrics(ctx context.Context, + l log.Logger, client *ethclient.Client, account common.Address) { + opmetrics.LaunchBalanceMetrics(ctx, l, m.registry, m.ns, client, account) +} + +// RecordInfo sets a pseudo-metric that contains versioning and +// config info for the op-proposer. +func (m *Metrics) RecordInfo(version string) { + m.Info.WithLabelValues(version).Set(1) +} + +// RecordUp sets the up metric to 1. +func (m *Metrics) RecordUp() { + prometheus.MustRegister() + m.Up.Set(1) +} + +const ( + BlockProposed = "proposed" +) + +// RecordL2BlocksProposed should be called when new L2 block is proposed +func (m *Metrics) RecordL2BlocksProposed(l2ref eth.L2BlockRef) { + m.RecordL2Ref(BlockProposed, l2ref) +} diff --git a/op-proposer/metrics/noop.go b/op-proposer/metrics/noop.go new file mode 100644 index 000000000..3ed4b0030 --- /dev/null +++ b/op-proposer/metrics/noop.go @@ -0,0 +1,13 @@ +package metrics + +import ( + "github.com/ethereum-optimism/optimism/op-node/eth" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" +) + +type noopMetrics struct{ opmetrics.NoopRefMetrics } + +func (*noopMetrics) RecordInfo(version string) {} +func (*noopMetrics) RecordUp() {} + +func (*noopMetrics) RecordL2BlocksProposed(l2ref eth.L2BlockRef) {} diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 901d0c2e7..5852166b0 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -13,6 +13,8 @@ import ( "syscall" "time" + "github.com/ethereum-optimism/optimism/op-proposer/metrics" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -26,7 +28,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/sources" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" oplog "github.com/ethereum-optimism/optimism/op-service/log" - opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/txmgr" @@ -49,9 +50,10 @@ func Main(version string, cliCtx *cli.Context) error { } l := oplog.NewLogger(cfg.LogConfig) + m := metrics.NewMetrics("default") l.Info("Initializing L2 Output Submitter") - l2OutputSubmitter, err := NewL2OutputSubmitterFromCLIConfig(cfg, l) + l2OutputSubmitter, err := NewL2OutputSubmitterFromCLIConfig(cfg, l, m) if err != nil { l.Error("Unable to create the L2 Output Submitter", "error", err) return err @@ -78,19 +80,15 @@ func Main(version string, cliCtx *cli.Context) error { }() } - registry := opmetrics.NewRegistry() metricsCfg := cfg.MetricsConfig if metricsCfg.Enabled { - metricsRegistry := opmetrics.InitProposerMetricsRegistry(registry, "") - l2OutputSubmitter.mr = metricsRegistry l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) go func() { - if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { + if err := m.Serve(ctx, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { l.Error("error starting metrics server", err) } }() - addr := l2OutputSubmitter.from - opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", l2OutputSubmitter.l1Client, addr) + m.StartBalanceMetrics(ctx, l, l2OutputSubmitter.l1Client, l2OutputSubmitter.from) } rpcCfg := cfg.RPCConfig @@ -119,8 +117,8 @@ type L2OutputSubmitter struct { wg sync.WaitGroup done chan struct{} log log.Logger - mr *opmetrics.ProposerMetricsRegistry metricsEnabled bool + metr metrics.Metricer ctx context.Context cancel context.CancelFunc @@ -147,7 +145,7 @@ type L2OutputSubmitter struct { } // NewL2OutputSubmitterFromCLIConfig creates a new L2 Output Submitter given the CLI Config -func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSubmitter, error) { +func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*L2OutputSubmitter, error) { signer, fromAddress, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, cfg.L2OutputHDPath, cfg.SignerConfig) if err != nil { return nil, err @@ -190,11 +188,11 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu metricsEnabled: cfg.MetricsConfig.Enabled, } - return NewL2OutputSubmitter(proposerCfg, l) + return NewL2OutputSubmitter(proposerCfg, l, m) } // NewL2OutputSubmitter creates a new L2 Output Submitter -func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) { +func NewL2OutputSubmitter(cfg Config, l log.Logger, m metrics.Metricer) (*L2OutputSubmitter, error) { ctx, cancel := context.WithCancel(context.Background()) cCtx, cCancel := context.WithTimeout(ctx, defaultDialTimeout) @@ -234,6 +232,7 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) ctx: ctx, cancel: cancel, metricsEnabled: cfg.metricsEnabled, + metr: m, l1Client: cfg.L1Client, rollupClient: cfg.RollupClient, @@ -383,7 +382,6 @@ func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Trans if l.metricsEnabled { // Emit the proposed block Number - opmetrics.EmitBlockNumber(l.mr.BlockNumberGauge, receipt.BlockNumber) } // The transaction was successfully submitted diff --git a/op-service/metrics/proposer.go b/op-service/metrics/proposer.go deleted file mode 100644 index cc05304c3..000000000 --- a/op-service/metrics/proposer.go +++ /dev/null @@ -1,25 +0,0 @@ -package metrics - -import ( - "math/big" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) - -type ProposerMetricsRegistry struct { - BlockNumberGauge prometheus.Gauge -} - -func InitProposerMetricsRegistry(r *prometheus.Registry, ns string) *ProposerMetricsRegistry { - blockNumberGauge := promauto.With(r).NewGauge(prometheus.GaugeOpts{ - Namespace: ns, - Name: "latest_block_number", - Help: "Latest L2 proposed block number", - }) - - return &ProposerMetricsRegistry{BlockNumberGauge: blockNumberGauge} -} -func EmitBlockNumber(gauge prometheus.Gauge, blockNumber *big.Int) { - gauge.Set(float64(blockNumber.Uint64())) -} From 8a2d7f5ab9ebe8e227f86e185a8c3b01feaafbc4 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 20 Mar 2023 15:10:14 -0700 Subject: [PATCH 101/404] missing noop --- op-proposer/metrics/noop.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/op-proposer/metrics/noop.go b/op-proposer/metrics/noop.go index 3ed4b0030..d5cf33e58 100644 --- a/op-proposer/metrics/noop.go +++ b/op-proposer/metrics/noop.go @@ -7,6 +7,8 @@ import ( type noopMetrics struct{ opmetrics.NoopRefMetrics } +var NoopMetrics Metricer = new(noopMetrics) + func (*noopMetrics) RecordInfo(version string) {} func (*noopMetrics) RecordUp() {} From 1bc9f6e4280bd0b1511afa86dac6c9cd92913a10 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 20 Mar 2023 15:43:30 -0700 Subject: [PATCH 102/404] test fix --- op-e2e/actions/l2_proposer.go | 3 ++- op-e2e/migration_test.go | 3 ++- op-e2e/setup.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/op-e2e/actions/l2_proposer.go b/op-e2e/actions/l2_proposer.go index cc31df477..d5347a79e 100644 --- a/op-e2e/actions/l2_proposer.go +++ b/op-e2e/actions/l2_proposer.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-node/sources" + "github.com/ethereum-optimism/optimism/op-proposer/metrics" "github.com/ethereum-optimism/optimism/op-proposer/proposer" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/txmgr" @@ -60,7 +61,7 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl SignerFnFactory: signer, } - dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log) + dr, err := proposer.NewL2OutputSubmitter(proposerCfg, log, metrics.NoopMetrics) require.NoError(t, err) return &L2Proposer{ diff --git a/op-e2e/migration_test.go b/op-e2e/migration_test.go index 69bf616e6..7ee2cead0 100644 --- a/op-e2e/migration_test.go +++ b/op-e2e/migration_test.go @@ -15,6 +15,7 @@ import ( batchermetrics "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/sources" + proposermetrics "github.com/ethereum-optimism/optimism/op-proposer/metrics" l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -362,7 +363,7 @@ func TestMigration(t *testing.T) { Format: "text", }, PrivateKey: hexPriv(secrets.Proposer), - }, lgr.New("module", "proposer")) + }, lgr.New("module", "proposer"), proposermetrics.NoopMetrics) require.NoError(t, err) t.Cleanup(func() { proposer.Stop() diff --git a/op-e2e/setup.go b/op-e2e/setup.go index fea710838..6108eddbf 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/testlog" + proposermetrics "github.com/ethereum-optimism/optimism/op-proposer/metrics" l2os "github.com/ethereum-optimism/optimism/op-proposer/proposer" oplog "github.com/ethereum-optimism/optimism/op-service/log" ) @@ -572,7 +573,7 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { Format: "text", }, PrivateKey: hexPriv(cfg.Secrets.Proposer), - }, sys.cfg.Loggers["proposer"]) + }, sys.cfg.Loggers["proposer"], proposermetrics.NoopMetrics) if err != nil { return nil, fmt.Errorf("unable to setup l2 output submitter: %w", err) } From cdac097002cff47f79190208a9b098144c2a7c7b Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 16 Mar 2023 15:20:36 +0100 Subject: [PATCH 103/404] op-node: generalize and improve alt-sync --- op-e2e/migration_test.go | 1 + op-e2e/setup.go | 14 ++- op-e2e/system_test.go | 26 +++--- op-node/flags/flags.go | 8 ++ op-node/node/client.go | 42 ++++----- op-node/node/config.go | 3 + op-node/node/node.go | 56 +++++++----- op-node/rollup/derive/engine_queue.go | 7 +- op-node/rollup/driver/driver.go | 15 +++- op-node/rollup/driver/state.go | 67 +++++++------- op-node/service.go | 1 + op-node/sources/sync_client.go | 120 +++++++++++++++++++------- 12 files changed, 235 insertions(+), 125 deletions(-) diff --git a/op-e2e/migration_test.go b/op-e2e/migration_test.go index 69bf616e6..1def369c5 100644 --- a/op-e2e/migration_test.go +++ b/op-e2e/migration_test.go @@ -277,6 +277,7 @@ func TestMigration(t *testing.T) { L2EngineAddr: gethNode.HTTPAuthEndpoint(), L2EngineJWTSecret: testingJWTSecret, }, + L2Sync: &node.PreparedL2SyncEndpoint{Client: nil, TrustRPC: false}, Driver: driver.Config{ VerifierConfDepth: 0, SequencerConfDepth: 0, diff --git a/op-e2e/setup.go b/op-e2e/setup.go index fea710838..be8352914 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -193,6 +193,9 @@ type SystemConfig struct { // If the proposer can make proposals for L2 blocks derived from L1 blocks which are not finalized on L1 yet. NonFinalizedProposals bool + + // Explicitly disable batcher, for tests that rely on unsafe L2 payloads + DisableBatcher bool } type System struct { @@ -417,6 +420,10 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { L2EngineAddr: l2EndpointConfig, L2EngineJWTSecret: cfg.JWTSecret, } + rollupCfg.L2Sync = &rollupNode.PreparedL2SyncEndpoint{ + Client: nil, + TrustRPC: false, + } } // Geth Clients @@ -606,8 +613,11 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { return nil, fmt.Errorf("failed to setup batch submitter: %w", err) } - if err := sys.BatchSubmitter.Start(); err != nil { - return nil, fmt.Errorf("unable to start batch submitter: %w", err) + // Batcher may be enabled later + if !sys.cfg.DisableBatcher { + if err := sys.BatchSubmitter.Start(); err != nil { + return nil, fmt.Errorf("unable to start batch submitter: %w", err) + } } return sys, nil diff --git a/op-e2e/system_test.go b/op-e2e/system_test.go index 2ac82bbd9..1e4170712 100644 --- a/op-e2e/system_test.go +++ b/op-e2e/system_test.go @@ -649,7 +649,7 @@ func TestSystemMockP2P(t *testing.T) { require.Contains(t, received, receiptVerif.BlockHash) } -// TestSystemMockP2P sets up a L1 Geth node, a rollup node, and a L2 geth node and then confirms that +// TestSystemRPCAltSync sets up a L1 Geth node, a rollup node, and a L2 geth node and then confirms that // the nodes can sync L2 blocks before they are confirmed on L1. // // Test steps: @@ -660,24 +660,28 @@ func TestSystemMockP2P(t *testing.T) { // 6. Wait for the RPC sync method to grab the block from the sequencer over RPC and insert it into the verifier's unsafe chain. // 7. Wait for the verifier to sync the unsafe chain into the safe chain. // 8. Verify that the TX is included in the verifier's safe chain. -func TestSystemMockAltSync(t *testing.T) { +func TestSystemRPCAltSync(t *testing.T) { parallel(t) if !verboseGethNodes { log.Root().SetHandler(log.DiscardHandler()) } cfg := DefaultSystemConfig(t) - // slow down L1 blocks so we can see the L2 blocks arrive well before the L1 blocks do. - // Keep the seq window small so the L2 chain is started quick - cfg.DeployConfig.L1BlockTime = 10 + // the default is nil, but this may change in the future. + // This test must ensure the blocks are not synced via Gossip, but instead via the alt RPC based sync. + cfg.P2PTopology = nil + // Disable batcher, so there will not be any L1 data to sync from + cfg.DisableBatcher = true - var published, received []common.Hash + var published, received []string seqTracer, verifTracer := new(FnTracer), new(FnTracer) + // The sequencer still publishes the blocks to the tracer, even if they do not reach the network due to disabled P2P seqTracer.OnPublishL2PayloadFn = func(ctx context.Context, payload *eth.ExecutionPayload) { - published = append(published, payload.BlockHash) + published = append(published, payload.ID().String()) } + // Blocks are now received via the RPC based alt-sync method verifTracer.OnUnsafeL2PayloadFn = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) { - received = append(received, payload.BlockHash) + received = append(received, payload.ID().String()) } cfg.Nodes["sequencer"].Tracer = seqTracer cfg.Nodes["verifier"].Tracer = verifTracer @@ -687,8 +691,8 @@ func TestSystemMockAltSync(t *testing.T) { role: "sequencer", action: func(sCfg *SystemConfig, system *System) { rpc, _ := system.Nodes["sequencer"].Attach() // never errors - cfg.Nodes["verifier"].L2Sync = &rollupNode.L2SyncRPCConfig{ - Rpc: client.NewBaseRPCClient(rpc), + cfg.Nodes["verifier"].L2Sync = &rollupNode.PreparedL2SyncEndpoint{ + Client: client.NewBaseRPCClient(rpc), } }, }) @@ -726,7 +730,7 @@ func TestSystemMockAltSync(t *testing.T) { require.Equal(t, receiptSeq, receiptVerif) // Verify that the tx was received via RPC sync (P2P is disabled) - require.Contains(t, received, receiptVerif.BlockHash) + require.Contains(t, received, eth.BlockID{Hash: receiptVerif.BlockHash, Number: receiptVerif.BlockNumber.Uint64()}.String()) // Verify that everything that was received was published require.GreaterOrEqual(t, len(published), len(received)) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 44f23ac3c..9c17cd762 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -175,6 +175,13 @@ var ( EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC"), Required: false, } + BackupL2UnsafeSyncRPCTrustRPC = cli.StringFlag{ + Name: "l2.backup-unsafe-sync-rpc.trustrpc", + Usage: "Like l1.trustrpc, configure if response data from the RPC needs to be verified, e.g. blockhash computation." + + "This does not include checks if the blockhash is part of the canonical chain.", + EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC_TRUST_RPC"), + Required: false, + } ) var requiredFlags = []cli.Flag{ @@ -207,6 +214,7 @@ var optionalFlags = []cli.Flag{ HeartbeatMonikerFlag, HeartbeatURLFlag, BackupL2UnsafeSyncRPC, + BackupL2UnsafeSyncRPCTrustRPC, } // Flags contains the list of configuration options available to the binary. diff --git a/op-node/node/client.go b/op-node/node/client.go index d83a258fb..73a2f7e46 100644 --- a/op-node/node/client.go +++ b/op-node/node/client.go @@ -20,7 +20,9 @@ type L2EndpointSetup interface { } type L2SyncEndpointSetup interface { - Setup(ctx context.Context, log log.Logger) (cl client.RPC, err error) + // Setup a RPC client to another L2 node to sync L2 blocks from. + // It may return a nil client with nil error if RPC based sync is not enabled. + Setup(ctx context.Context, log log.Logger) (cl client.RPC, trust bool, err error) Check() error } @@ -82,45 +84,45 @@ func (p *PreparedL2Endpoints) Setup(ctx context.Context, log log.Logger) (client // L2SyncEndpointConfig contains configuration for the fallback sync endpoint type L2SyncEndpointConfig struct { - // Address of the L2 RPC to use for backup sync + // Address of the L2 RPC to use for backup sync, may be empty if RPC alt-sync is disabled. L2NodeAddr string + TrustRPC bool } var _ L2SyncEndpointSetup = (*L2SyncEndpointConfig)(nil) -func (cfg *L2SyncEndpointConfig) Setup(ctx context.Context, log log.Logger) (client.RPC, error) { +// Setup creates an RPC client to sync from. +// It will return nil without error if no sync method is configured. +func (cfg *L2SyncEndpointConfig) Setup(ctx context.Context, log log.Logger) (cl client.RPC, trust bool, err error) { + if cfg.L2NodeAddr == "" { + return nil, false, nil + } l2Node, err := client.NewRPC(ctx, log, cfg.L2NodeAddr) if err != nil { - return nil, err + return nil, false, err } - return l2Node, nil + return l2Node, cfg.TrustRPC, nil } func (cfg *L2SyncEndpointConfig) Check() error { - if cfg.L2NodeAddr == "" { - return errors.New("empty L2 Node Address") - } - + // empty addr is valid, as it is optional. return nil } -type L2SyncRPCConfig struct { - // RPC endpoint to use for syncing - Rpc client.RPC +type PreparedL2SyncEndpoint struct { + // RPC endpoint to use for syncing, may be nil if RPC alt-sync is disabled. + Client client.RPC + TrustRPC bool } -var _ L2SyncEndpointSetup = (*L2SyncRPCConfig)(nil) +var _ L2SyncEndpointSetup = (*PreparedL2SyncEndpoint)(nil) -func (cfg *L2SyncRPCConfig) Setup(ctx context.Context, log log.Logger) (client.RPC, error) { - return cfg.Rpc, nil +func (cfg *PreparedL2SyncEndpoint) Setup(ctx context.Context, log log.Logger) (cl client.RPC, trust bool, err error) { + return cfg.Client, cfg.TrustRPC, nil } -func (cfg *L2SyncRPCConfig) Check() error { - if cfg.Rpc == nil { - return errors.New("rpc cannot be nil") - } - +func (cfg *PreparedL2SyncEndpoint) Check() error { return nil } diff --git a/op-node/node/config.go b/op-node/node/config.go index 8d95f79f3..37533a868 100644 --- a/op-node/node/config.go +++ b/op-node/node/config.go @@ -80,6 +80,9 @@ func (cfg *Config) Check() error { if err := cfg.L2.Check(); err != nil { return fmt.Errorf("l2 endpoint config error: %w", err) } + if err := cfg.L2Sync.Check(); err != nil { + return fmt.Errorf("sync config error: %w", err) + } if err := cfg.Rollup.Check(); err != nil { return fmt.Errorf("rollup config error: %w", err) } diff --git a/op-node/node/node.go b/op-node/node/node.go index 2c2e43521..8bfb4a68e 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -33,6 +33,7 @@ type OpNode struct { l1Source *sources.L1Client // L1 Client to fetch data from l2Driver *driver.Driver // L2 Engine to Sync l2Source *sources.EngineClient // L2 Execution Engine RPC bindings + rpcSync *sources.SyncClient // Alt-sync RPC client, optional (may be nil) server *rpcServer // RPC server hosting the rollup-node API p2pNode *p2p.NodeP2P // P2P node functionality p2pSigner p2p.Signer // p2p gogssip application messages will be signed with this signer @@ -86,6 +87,9 @@ func (n *OpNode) init(ctx context.Context, cfg *Config, snapshotLog log.Logger) if err := n.initL2(ctx, cfg, snapshotLog); err != nil { return err } + if err := n.initRPCSync(ctx, cfg); err != nil { + return err + } if err := n.initP2PSigner(ctx, cfg); err != nil { return err } @@ -197,29 +201,28 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger return err } - var syncClient *sources.SyncClient - // If the L2 sync config is present, use it to create a sync client - if cfg.L2Sync != nil { - if err := cfg.L2Sync.Check(); err != nil { - log.Info("L2 sync config is not present, skipping L2 sync client setup", "err", err) - } else { - rpcSyncClient, err := cfg.L2Sync.Setup(ctx, n.log) - if err != nil { - return fmt.Errorf("failed to setup L2 execution-engine RPC client for backup sync: %w", err) - } + n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n, n, n.log, snapshotLog, n.metrics) - // The sync client's RPC is always trusted - config := sources.SyncClientDefaultConfig(&cfg.Rollup, true) + return nil +} - syncClient, err = sources.NewSyncClient(n.OnUnsafeL2Payload, rpcSyncClient, n.log, n.metrics.L2SourceCache, config) - if err != nil { - return fmt.Errorf("failed to create sync client: %w", err) - } - } +func (n *OpNode) initRPCSync(ctx context.Context, cfg *Config) error { + rpcSyncClient, trustRPC, err := cfg.L2Sync.Setup(ctx, n.log) + if err != nil { + return fmt.Errorf("failed to setup L2 execution-engine RPC client for backup sync: %w", err) + } + if rpcSyncClient == nil { // if no RPC client is configured to sync from, then don't add the RPC sync client + return nil } - n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, syncClient, n, n.log, snapshotLog, n.metrics) + // The sync client's RPC is always trusted + config := sources.SyncClientDefaultConfig(&cfg.Rollup, trustRPC) + syncClient, err := sources.NewSyncClient(n.OnUnsafeL2Payload, rpcSyncClient, n.log, n.metrics.L2SourceCache, config) + if err != nil { + return fmt.Errorf("failed to create sync client: %w", err) + } + n.rpcSync = syncClient return nil } @@ -292,11 +295,12 @@ func (n *OpNode) Start(ctx context.Context) error { } // If the backup unsafe sync client is enabled, start its event loop - if n.l2Driver.L2SyncCl != nil { - if err := n.l2Driver.L2SyncCl.Start(); err != nil { + if n.rpcSync != nil { + if err := n.rpcSync.Start(); err != nil { n.log.Error("Could not start the backup sync client", "err", err) return err } + n.log.Info("Started L2-RPC sync service") } return nil @@ -375,6 +379,14 @@ func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *e return nil } +func (n *OpNode) RequestL2Range(ctx context.Context, start, end uint64) error { + if n.rpcSync != nil { + return n.rpcSync.RequestL2Range(ctx, start, end) + } + n.log.Debug("ignoring request to sync L2 range, no sync method available") + return nil +} + func (n *OpNode) P2P() p2p.Node { return n.p2pNode } @@ -413,8 +425,8 @@ func (n *OpNode) Close() error { } // If the L2 sync client is present & running, close it. - if n.l2Driver.L2SyncCl != nil { - if err := n.l2Driver.L2SyncCl.Close(); err != nil { + if n.rpcSync != nil { + if err := n.rpcSync.Close(); err != nil { result = multierror.Append(result, fmt.Errorf("failed to close L2 engine backup sync client cleanly: %w", err)) } } diff --git a/op-node/rollup/derive/engine_queue.go b/op-node/rollup/derive/engine_queue.go index 91a26cc46..a9d49abc5 100644 --- a/op-node/rollup/derive/engine_queue.go +++ b/op-node/rollup/derive/engine_queue.go @@ -682,7 +682,8 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System return io.EOF } -// GetUnsafeQueueGap retrieves the current [start, end] range of the gap between the tip of the unsafe priority queue and the unsafe head. +// GetUnsafeQueueGap retrieves the current [start, end) range (incl. start, excl. end) +// of the gap between the tip of the unsafe priority queue and the unsafe head. // If there is no gap, the difference between end and start will be 0. func (eq *EngineQueue) GetUnsafeQueueGap(expectedNumber uint64) (start uint64, end uint64) { // The start of the gap is always the unsafe head + 1 @@ -691,9 +692,11 @@ func (eq *EngineQueue) GetUnsafeQueueGap(expectedNumber uint64) (start uint64, e // If the priority queue is empty, the end is the first block number at the top of the priority queue // Otherwise, the end is the expected block number if first := eq.unsafePayloads.Peek(); first != nil { + // Don't include the payload we already have in the sync range end = first.ID().Number } else { - end = expectedNumber + // Include the expected payload in the sync range + end = expectedNumber + 1 } return start, end diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index d94eada3c..f3c927e1a 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-node/sources" ) type Metrics interface { @@ -82,8 +81,18 @@ type Network interface { PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayload) error } +type AltSync interface { + // RequestL2Range informs the sync source that the given range of L2 blocks is missing, + // and should be retrieved from any available alternative syncing source. + // The sync results should be returned back to the driver via the OnUnsafeL2Payload(ctx, payload) method. + // The latest requested range should always take priority over previous requests. + // There may be overlaps in requested ranges. + // An error may be returned if the scheduling fails immediately, e.g. a context timeout. + RequestL2Range(ctx context.Context, start, end uint64) error +} + // NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks. -func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, syncClient *sources.SyncClient, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver { +func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, altSync AltSync, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver { l1State := NewL1State(log, metrics) sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) @@ -115,6 +124,6 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, sy l1SafeSig: make(chan eth.L1BlockRef, 10), l1FinalizedSig: make(chan eth.L1BlockRef, 10), unsafeL2Payloads: make(chan *eth.ExecutionPayload, 10), - L2SyncCl: syncClient, + altSync: altSync, } } diff --git a/op-node/rollup/driver/state.go b/op-node/rollup/driver/state.go index 1387c9136..87a1b5a2b 100644 --- a/op-node/rollup/driver/state.go +++ b/op-node/rollup/driver/state.go @@ -16,7 +16,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-service/backoff" ) @@ -64,8 +63,8 @@ type Driver struct { l1SafeSig chan eth.L1BlockRef l1FinalizedSig chan eth.L1BlockRef - // Backup unsafe sync client - L2SyncCl *sources.SyncClient + // Interface to signal the L2 block range to sync. + altSync AltSync // L2 Signals: @@ -200,11 +199,12 @@ func (s *Driver) eventLoop() { sequencerTimer.Reset(delay) } - // Create a ticker to check if there is a gap in the engine queue every 15 seconds - // If there is, we send requests to the backup RPC to retrieve the missing payloads - // and add them to the unsafe queue. - altSyncTicker := time.NewTicker(15 * time.Second) + // Create a ticker to check if there is a gap in the engine queue, whenever + // If there is, we send requests to sync source to retrieve the missing payloads. + syncCheckInterval := time.Duration(s.config.BlockTime) * time.Second * 2 + altSyncTicker := time.NewTicker(syncCheckInterval) defer altSyncTicker.Stop() + lastUnsafeL2 := s.derivation.UnsafeL2Head() for { // If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action. @@ -220,6 +220,13 @@ func (s *Driver) eventLoop() { sequencerCh = nil } + // If the engine is not ready, or if the L2 head is actively changing, then reset the alt-sync: + // there is no need to request L2 blocks when we are syncing already. + if head := s.derivation.UnsafeL2Head(); head != lastUnsafeL2 || !s.derivation.EngineReady() { + lastUnsafeL2 = head + altSyncTicker.Reset(syncCheckInterval) + } + select { case <-sequencerCh: payload, err := s.sequencer.RunNextSequencerAction(ctx) @@ -237,10 +244,12 @@ func (s *Driver) eventLoop() { } planSequencerAction() // schedule the next sequencer action to keep the sequencing looping case <-altSyncTicker.C: - // Check if there is a gap in the current unsafe payload queue. If there is, attempt to fetch - // missing payloads from the backup RPC (if it is configured). - if s.L2SyncCl != nil { - s.checkForGapInUnsafeQueue(ctx) + // Check if there is a gap in the current unsafe payload queue. + ctx, cancel := context.WithTimeout(ctx, time.Second*2) + err := s.checkForGapInUnsafeQueue(ctx) + cancel() + if err != nil { + s.log.Warn("failed to check for unsafe L2 blocks to sync", "err", err) } case payload := <-s.unsafeL2Payloads: s.snapshot("New unsafe payload") @@ -462,35 +471,29 @@ type hashAndErrorChannel struct { err chan error } -// checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads from the backup RPC. -// WARNING: The sync client's attempt to retrieve the missing payloads is not guaranteed to succeed, and it will fail silently (besides -// emitting warning logs) if the requests fail. -func (s *Driver) checkForGapInUnsafeQueue(ctx context.Context) { +// checkForGapInUnsafeQueue checks if there is a gap in the unsafe queue and attempts to retrieve the missing payloads from an alt-sync method. +// WARNING: This is only an outgoing signal, the blocks are not guaranteed to be retrieved. +// Results are received through OnUnsafeL2Payload. +func (s *Driver) checkForGapInUnsafeQueue(ctx context.Context) error { // subtract genesis time from wall clock to get the time elapsed since genesis, and then divide that // difference by the block time to get the expected L2 block number at the current time. If the // unsafe head does not have this block number, then there is a gap in the queue. wallClock := uint64(time.Now().Unix()) genesisTimestamp := s.config.Genesis.L2Time + if wallClock < genesisTimestamp { + s.log.Debug("nothing to sync, did not reach genesis L2 time yet", "genesis", genesisTimestamp) + return nil + } wallClockGenesisDiff := wallClock - genesisTimestamp - expectedL2Block := wallClockGenesisDiff / s.config.BlockTime + // Note: round down, we should not request blocks into the future. + blocksSinceGenesis := wallClockGenesisDiff / s.config.BlockTime + expectedL2Block := s.config.Genesis.L2.Number + blocksSinceGenesis start, end := s.derivation.GetUnsafeQueueGap(expectedL2Block) - size := end - start - // Check if there is a gap between the unsafe head and the expected L2 block number at the current time. - if size > 0 { - s.log.Warn("Gap in payload queue tip and expected unsafe chain detected", "start", start, "end", end, "size", size) - s.log.Info("Attempting to fetch missing payloads from backup RPC", "start", start, "end", end, "size", size) - - // Attempt to fetch the missing payloads from the backup unsafe sync RPC concurrently. - // Concurrent requests are safe here due to the engine queue being a priority queue. - for blockNumber := start; blockNumber <= end; blockNumber++ { - select { - case s.L2SyncCl.FetchUnsafeBlock <- blockNumber: - // Do nothing- the block number was successfully sent into the channel - default: - return // If the channel is full, return and wait for the next iteration of the event loop - } - } + if end > start { + s.log.Debug("requesting missing unsafe L2 block range", "start", start, "end", end, "size", end-start) + return s.altSync.RequestL2Range(ctx, start, end) } + return nil } diff --git a/op-node/service.go b/op-node/service.go index 48848babc..0656affac 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -136,6 +136,7 @@ func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConf func NewL2SyncEndpointConfig(ctx *cli.Context) *node.L2SyncEndpointConfig { return &node.L2SyncEndpointConfig{ L2NodeAddr: ctx.GlobalString(flags.BackupL2UnsafeSyncRPC.Name), + TrustRPC: ctx.GlobalBool(flags.BackupL2UnsafeSyncRPCTrustRPC.Name), } } diff --git a/op-node/sources/sync_client.go b/op-node/sources/sync_client.go index 6c5aa6dcc..b6e41c05d 100644 --- a/op-node/sources/sync_client.go +++ b/op-node/sources/sync_client.go @@ -3,7 +3,10 @@ package sources import ( "context" "errors" + "fmt" + "io" "sync" + "time" "github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/eth" @@ -18,23 +21,34 @@ var ErrNoUnsafeL2PayloadChannel = errors.New("unsafeL2Payloads channel must not // RpcSyncPeer is a mock PeerID for the RPC sync client. var RpcSyncPeer peer.ID = "ALT_RPC_SYNC" +// Limit the maximum range to request at a time. +const maxRangePerWorker = 10 + type receivePayload = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) error -type SyncClientInterface interface { +type syncRequest struct { + start, end uint64 +} + +type RPCSync interface { + io.Closer + // Start starts an additional worker syncing job Start() error - Close() error - fetchUnsafeBlockFromRpc(ctx context.Context, blockNumber uint64) + // RequestL2Range signals that the given range should be fetched, implementing the alt-sync interface. + RequestL2Range(ctx context.Context, start, end uint64) error } type SyncClient struct { *L2Client - FetchUnsafeBlock chan uint64 - done chan struct{} - receivePayload receivePayload - wg sync.WaitGroup -} -var _ SyncClientInterface = (*SyncClient)(nil) + requests chan syncRequest + + resCtx context.Context + resCancel context.CancelFunc + + receivePayload receivePayload + wg sync.WaitGroup +} type SyncClientConfig struct { L2ClientConfig @@ -51,30 +65,58 @@ func NewSyncClient(receiver receivePayload, client client.RPC, log log.Logger, m if err != nil { return nil, err } - + // This resource context is shared between all workers that may be started + resCtx, resCancel := context.WithCancel(context.Background()) return &SyncClient{ - L2Client: l2Client, - FetchUnsafeBlock: make(chan uint64, 128), - done: make(chan struct{}), - receivePayload: receiver, + L2Client: l2Client, + resCtx: resCtx, + resCancel: resCancel, + requests: make(chan syncRequest, 128), + receivePayload: receiver, }, nil } -// Start starts up the state loop. -// The loop will have been started if err is not nil. +// Start starts the syncing background work. This may not be called after Close(). func (s *SyncClient) Start() error { + // TODO(CLI-3635): we can start multiple event loop runners as workers, to parallelize the work s.wg.Add(1) go s.eventLoop() return nil } -// Close sends a signal to the event loop to stop. +// Close sends a signal to close all concurrent syncing work. func (s *SyncClient) Close() error { - s.done <- struct{}{} + s.resCancel() s.wg.Wait() return nil } +func (s *SyncClient) RequestL2Range(ctx context.Context, start, end uint64) error { + // Drain previous requests now that we have new information + for len(s.requests) > 0 { + <-s.requests + } + + // TODO(CLI-3635): optimize the by-range fetching with the Engine API payloads-by-range method. + + s.log.Info("Scheduling to fetch missing payloads from backup RPC", "start", start, "end", end, "size", end-start) + + for i := start; i < end; i += maxRangePerWorker { + r := syncRequest{start: i, end: i + maxRangePerWorker} + if r.end > end { + r.end = end + } + s.log.Info("Scheduling range request", "start", r.start, "end", r.end, "size", r.end-r.start) + // schedule new range to be requested + select { + case s.requests <- r: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil +} + // eventLoop is the main event loop for the sync client. func (s *SyncClient) eventLoop() { defer s.wg.Done() @@ -82,10 +124,28 @@ func (s *SyncClient) eventLoop() { for { select { - case <-s.done: + case <-s.resCtx.Done(): + s.log.Debug("Shutting down RPC sync worker") return - case blockNumber := <-s.FetchUnsafeBlock: - s.fetchUnsafeBlockFromRpc(context.Background(), blockNumber) + case r := <-s.requests: + // Limit the maximum time for fetching payloads + ctx, cancel := context.WithTimeout(s.resCtx, time.Second*10) + // We are only fetching one block at a time here. + err := s.fetchUnsafeBlockFromRpc(ctx, r.start) + cancel() + if err != nil { + s.log.Error("failed syncing L2 block via RPC", "err", err) + } else { + r.start += 1 // continue with next block + } + // Reschedule + if r.start < r.end { + select { + case s.requests <- r: + default: + // drop syncing job if we are too busy with sync jobs already. + } + } } } } @@ -95,28 +155,22 @@ func (s *SyncClient) eventLoop() { // // Post Shanghai hardfork, the engine API's `PayloadBodiesByRange` method will be much more efficient, but for now, // the `eth_getBlockByNumber` method is more widely available. -func (s *SyncClient) fetchUnsafeBlockFromRpc(ctx context.Context, blockNumber uint64) { +func (s *SyncClient) fetchUnsafeBlockFromRpc(ctx context.Context, blockNumber uint64) error { s.log.Info("Requesting unsafe payload from backup RPC", "block number", blockNumber) payload, err := s.PayloadByNumber(ctx, blockNumber) if err != nil { - s.log.Warn("Failed to convert block to execution payload", "block number", blockNumber, "err", err) - return - } - - // Signature validation is not necessary here since the backup RPC is trusted. - if _, ok := payload.CheckBlockHash(); !ok { - s.log.Warn("Received invalid payload from backup RPC; invalid block hash", "payload", payload.ID()) - return + return fmt.Errorf("failed to fetch payload by number (%d): %w", blockNumber, err) } + // Note: the underlying RPC client used for syncing verifies the execution payload blockhash, if set to untrusted. s.log.Info("Received unsafe payload from backup RPC", "payload", payload.ID()) // Send the retrieved payload to the `unsafeL2Payloads` channel. if err = s.receivePayload(ctx, RpcSyncPeer, payload); err != nil { - s.log.Warn("Failed to send payload into the driver's unsafeL2Payloads channel", "payload", payload.ID(), "err", err) - return + return fmt.Errorf("failed to send payload %s into the driver's unsafeL2Payloads channel: %w", payload.ID(), err) } else { - s.log.Info("Sent received payload into the driver's unsafeL2Payloads channel", "payload", payload.ID()) + s.log.Debug("Sent received payload into the driver's unsafeL2Payloads channel", "payload", payload.ID()) + return nil } } From e6d58cdcefb17596ba06062260757f5306c46515 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 20 Mar 2023 16:07:16 -0700 Subject: [PATCH 104/404] record l2 block --- op-proposer/proposer/l2_output_submitter.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 5852166b0..9c0ac0999 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -382,6 +382,12 @@ func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Trans if l.metricsEnabled { // Emit the proposed block Number + block, err := l.rollupClient.OutputAtBlock(ctx, receipt.BlockNumber.Uint64()) + if err != nil { + l.log.Warn("unable to fetch block", "block_number", receipt.BlockNumber) + } else { + l.metr.RecordL2BlocksProposed(block.BlockRef) + } } // The transaction was successfully submitted From eda69a4852e73c071fe97abdc0a966e0c8002de3 Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 20 Mar 2023 16:16:55 -0700 Subject: [PATCH 105/404] emit from the caller --- op-proposer/proposer/l2_output_submitter.go | 38 ++++++++------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 9c0ac0999..18784b722 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -98,6 +98,9 @@ func Main(version string, cliCtx *cli.Context) error { return fmt.Errorf("error starting RPC server: %w", err) } + m.RecordInfo(version) + m.RecordUp() + interruptChannel := make(chan os.Signal, 1) signal.Notify(interruptChannel, []os.Signal{ os.Interrupt, @@ -113,12 +116,11 @@ func Main(version string, cliCtx *cli.Context) error { // L2OutputSubmitter is responsible for proposing outputs type L2OutputSubmitter struct { - txMgr txmgr.TxManager - wg sync.WaitGroup - done chan struct{} - log log.Logger - metricsEnabled bool - metr metrics.Metricer + txMgr txmgr.TxManager + wg sync.WaitGroup + done chan struct{} + log log.Logger + metr metrics.Metricer ctx context.Context cancel context.CancelFunc @@ -226,13 +228,12 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger, m metrics.Metricer) (*L2Outp rawL2ooContract := bind.NewBoundContract(cfg.L2OutputOracleAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client) return &L2OutputSubmitter{ - txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), - done: make(chan struct{}), - log: l, - ctx: ctx, - cancel: cancel, - metricsEnabled: cfg.metricsEnabled, - metr: m, + txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client), + done: make(chan struct{}), + log: l, + ctx: ctx, + cancel: cancel, + metr: m, l1Client: cfg.L1Client, rollupClient: cfg.RollupClient, @@ -380,16 +381,6 @@ func (l *L2OutputSubmitter) SendTransaction(ctx context.Context, tx *types.Trans return err } - if l.metricsEnabled { - // Emit the proposed block Number - block, err := l.rollupClient.OutputAtBlock(ctx, receipt.BlockNumber.Uint64()) - if err != nil { - l.log.Warn("unable to fetch block", "block_number", receipt.BlockNumber) - } else { - l.metr.RecordL2BlocksProposed(block.BlockRef) - } - } - // The transaction was successfully submitted l.log.Info("proposer tx successfully published", "tx_hash", receipt.TxHash) return nil @@ -429,6 +420,7 @@ func (l *L2OutputSubmitter) loop() { cancel() break } else { + l.metr.RecordL2BlocksProposed(output.BlockRef) cancel() } From be20409cf7ed8455b4cbc995af9ce2c7ce4703b2 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Mon, 20 Mar 2023 18:23:05 -0500 Subject: [PATCH 106/404] feat(docs/op-stack): Add the getting in touch form Closing: DEVRL-777 --- docs/op-stack/src/.vuepress/theme/components/PageMeta.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/op-stack/src/.vuepress/theme/components/PageMeta.vue b/docs/op-stack/src/.vuepress/theme/components/PageMeta.vue index c70c35427..cfb021035 100644 --- a/docs/op-stack/src/.vuepress/theme/components/PageMeta.vue +++ b/docs/op-stack/src/.vuepress/theme/components/PageMeta.vue @@ -32,6 +32,11 @@ Discord community +
  • + + Get support for going live + +
  • From 319279334f630ffb1f75b6376176a642e0a164a8 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 21 Mar 2023 00:40:21 +0100 Subject: [PATCH 107/404] op-node: RPC alt-sync comment review fixes Co-authored-by: Adrian Sutton --- op-node/node/node.go | 1 - op-node/rollup/driver/driver.go | 1 + op-node/rollup/driver/state.go | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/op-node/node/node.go b/op-node/node/node.go index 8bfb4a68e..d9b38c016 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -215,7 +215,6 @@ func (n *OpNode) initRPCSync(ctx context.Context, cfg *Config) error { return nil } - // The sync client's RPC is always trusted config := sources.SyncClientDefaultConfig(&cfg.Rollup, trustRPC) syncClient, err := sources.NewSyncClient(n.OnUnsafeL2Payload, rpcSyncClient, n.log, n.metrics.L2SourceCache, config) diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index f3c927e1a..2881d6aa7 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -84,6 +84,7 @@ type Network interface { type AltSync interface { // RequestL2Range informs the sync source that the given range of L2 blocks is missing, // and should be retrieved from any available alternative syncing source. + // The start of the range is inclusive, the end is exclusive. // The sync results should be returned back to the driver via the OnUnsafeL2Payload(ctx, payload) method. // The latest requested range should always take priority over previous requests. // There may be overlaps in requested ranges. diff --git a/op-node/rollup/driver/state.go b/op-node/rollup/driver/state.go index 87a1b5a2b..5e0dbceb1 100644 --- a/op-node/rollup/driver/state.go +++ b/op-node/rollup/driver/state.go @@ -199,8 +199,8 @@ func (s *Driver) eventLoop() { sequencerTimer.Reset(delay) } - // Create a ticker to check if there is a gap in the engine queue, whenever - // If there is, we send requests to sync source to retrieve the missing payloads. + // Create a ticker to check if there is a gap in the engine queue. Whenever + // there is, we send requests to sync source to retrieve the missing payloads. syncCheckInterval := time.Duration(s.config.BlockTime) * time.Second * 2 altSyncTicker := time.NewTicker(syncCheckInterval) defer altSyncTicker.Stop() From 2920aaee03492b1a614197ffce58070053bbe8ac Mon Sep 17 00:00:00 2001 From: Madhur Shrimal Date: Mon, 20 Mar 2023 16:40:54 -0700 Subject: [PATCH 108/404] remove unnecessary fields --- op-proposer/proposer/config.go | 1 - op-proposer/proposer/l2_output_submitter.go | 1 - 2 files changed, 2 deletions(-) diff --git a/op-proposer/proposer/config.go b/op-proposer/proposer/config.go index bc851dbcb..2fbd9c29e 100644 --- a/op-proposer/proposer/config.go +++ b/op-proposer/proposer/config.go @@ -30,7 +30,6 @@ type Config struct { AllowNonFinalized bool From common.Address SignerFnFactory opcrypto.SignerFactory - metricsEnabled bool } // CLIConfig is a well typed config that is parsed from the CLI params. diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go index 18784b722..a0b07c482 100644 --- a/op-proposer/proposer/l2_output_submitter.go +++ b/op-proposer/proposer/l2_output_submitter.go @@ -187,7 +187,6 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Me AllowNonFinalized: cfg.AllowNonFinalized, From: fromAddress, SignerFnFactory: signer, - metricsEnabled: cfg.MetricsConfig.Enabled, } return NewL2OutputSubmitter(proposerCfg, l, m) From 5ae0d8cb3a8f74f9d22df9951ffef6aa7123040a Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 17 Mar 2023 11:57:23 +1000 Subject: [PATCH 109/404] specs: Update derivation spec to match the current implementation Dropping frames due to timed out channels or duplication is only done for channels that have not yet been pruned from the channel bank. Timed out channels are only pruned from the channel-bank if they are prior to a readable channel. Explicitly state that channels are removed from the channel-bank when they are read. --- specs/derivation.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/specs/derivation.md b/specs/derivation.md index 4dcd01ee2..2fc7e55e9 100644 --- a/specs/derivation.md +++ b/specs/derivation.md @@ -517,12 +517,14 @@ New frames for timed-out channels are dropped instead of buffered. The channel-bank can only output data from the first opened channel. -Upon reading, first all timed-out channels are dropped. +Upon reading, while the first opened channel is timed-out, remove it from the channel-bank. -After pruning timed-out channels, the first remaining channel, if any, is read if it is ready: +Once the first opened channel, if any, is not timed-out and is ready, then it is read and removed from the channel-bank. -- The channel must be closed -- The channel must have a contiguous sequence of frames until the closing frame +A channel is ready if: + +- The channel is closed +- The channel has a contiguous sequence of frames until the closing frame If no channel is ready, the next frame is read and ingested into the channel bank. @@ -533,12 +535,16 @@ a new channel is opened, tagged with the current L1 block, and appended to the c Frame insertion conditions: -- New frames matching existing timed-out channels are dropped. -- Duplicate frames (by frame number) are dropped. -- Duplicate closes (new frame `is_last == 1`, but the channel has already seen a closing frame) are dropped. +- New frames matching timed-out channels that have not yet been pruned from the channel-bank are dropped. +- Duplicate frames (by frame number) for frames that have not yet been pruned from the channel-bank are dropped. +- Duplicate closes (new frame `is_last == 1`, but the channel has already seen a closing frame and has not yet been + pruned from the channel-bank) are dropped. If a frame is closing (`is_last == 1`) any existing higher-numbered frames are removed from the channel. +Note that while this allows channel IDs to be reused once they have been pruned from the channel-bank, it is recommended +that batcher implementations use unique channel IDs. + ### Channel Reader (Batch Decoding) In this stage, we decompress the channel we pull from the last stage, and then parse From 3acdf06a8fb536e6d266bf2ff0b3483b9b1ad0b1 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 21 Mar 2023 01:19:57 +0100 Subject: [PATCH 110/404] op-wheel: updates for geth Vana update --- op-wheel/cheat/cheat.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/op-wheel/cheat/cheat.go b/op-wheel/cheat/cheat.go index 835bc3ffe..32312515f 100644 --- a/op-wheel/cheat/cheat.go +++ b/op-wheel/cheat/cheat.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -80,11 +81,11 @@ type HeadFn func(headState *state.StateDB) error // and updates the blockchain headers indexes to reflect the new state-root, so geth will believe the cheat // (unless it ever re-applies the block). func (ch *Cheater) RunAndClose(fn HeadFn) error { - preBlock := ch.Blockchain.CurrentBlock() - if a, b := preBlock.NumberU64(), ch.Blockchain.Genesis().NumberU64(); a <= b { + preHeader := ch.Blockchain.CurrentBlock() + if a, b := preHeader.Number.Uint64(), ch.Blockchain.Genesis().NumberU64(); a <= b { return fmt.Errorf("cheating at genesis (head block %d <= genesis block %d) is not supported", a, b) } - state, err := ch.Blockchain.StateAt(preBlock.Root()) + state, err := ch.Blockchain.StateAt(preHeader.Root) if err != nil { _ = ch.Close() return fmt.Errorf("failed to look up head state: %w", err) @@ -103,7 +104,7 @@ func (ch *Cheater) RunAndClose(fn HeadFn) error { _ = ch.Close() return fmt.Errorf("failed to commit state change: %w", err) } - header := preBlock.Header() + header := preHeader // copy the header header.Root = stateRoot blockHash := header.Hash() @@ -115,14 +116,15 @@ func (ch *Cheater) RunAndClose(fn HeadFn) error { // based on core.BlockChain.writeHeadBlock: // Add the block to the canonical chain number scheme and mark as the head batch := ch.DB.NewBatch() - if ch.Blockchain.CurrentFinalizedBlock().Hash() == preBlock.Hash() { + preID := eth.BlockID{Hash: preHeader.Hash(), Number: preHeader.Number.Uint64()} + if ch.Blockchain.CurrentFinalBlock().Hash() == preID.Hash { rawdb.WriteFinalizedBlockHash(batch, blockHash) } - rawdb.DeleteHeaderNumber(batch, preBlock.Hash()) + rawdb.DeleteHeaderNumber(batch, preHeader.Hash()) rawdb.WriteHeadHeaderHash(batch, blockHash) rawdb.WriteHeadFastBlockHash(batch, blockHash) - rawdb.WriteCanonicalHash(batch, blockHash, preBlock.NumberU64()) - rawdb.WriteHeaderNumber(batch, blockHash, preBlock.NumberU64()) + rawdb.WriteCanonicalHash(batch, blockHash, preID.Number) + rawdb.WriteHeaderNumber(batch, blockHash, preID.Number) rawdb.WriteHeader(batch, header) // not keyed by blockhash, and we didn't remove any txs, so we just leave this one as-is. // rawdb.WriteTxLookupEntriesByBlock(batch, block) @@ -131,17 +133,17 @@ func (ch *Cheater) RunAndClose(fn HeadFn) error { // Geth stores the TD for each block separately from the block itself. We must update this // manually, otherwise Geth thinks we haven't reached TTD yet and tries to build a block // using Clique consensus, which causes a panic. - rawdb.WriteTd(batch, blockHash, preBlock.NumberU64(), ch.Blockchain.GetTd(preBlock.Hash(), preBlock.NumberU64())) + rawdb.WriteTd(batch, blockHash, preID.Number, ch.Blockchain.GetTd(preID.Hash, preID.Number)) // Need to copy over receipts since they are keyed by block hash. - receipts := rawdb.ReadReceipts(ch.DB, preBlock.Hash(), preBlock.NumberU64(), ch.Blockchain.Config()) - rawdb.WriteReceipts(batch, blockHash, preBlock.NumberU64(), receipts) + receipts := rawdb.ReadReceipts(ch.DB, preID.Hash, preID.Number, ch.Blockchain.Config()) + rawdb.WriteReceipts(batch, blockHash, preID.Number, receipts) // Geth maintains an internal mapping between block bodies and their hashes. None of the database // accessors above update this mapping, so we need to do it manually. - oldKey := blockBodyKey(preBlock.NumberU64(), preBlock.Hash()) - oldBody := rawdb.ReadBodyRLP(ch.DB, preBlock.Hash(), preBlock.NumberU64()) - newKey := blockBodyKey(preBlock.NumberU64(), blockHash) + oldKey := blockBodyKey(preID.Number, preID.Hash) + oldBody := rawdb.ReadBodyRLP(ch.DB, preID.Hash, preID.Number) + newKey := blockBodyKey(preID.Number, blockHash) if err := batch.Delete(oldKey); err != nil { return fmt.Errorf("error deleting old block body key") } From bd8c8e3a2225e99fe046d973d909113581e1d50d Mon Sep 17 00:00:00 2001 From: Mark Tyneway Date: Mon, 20 Mar 2023 10:36:38 -0700 Subject: [PATCH 111/404] contracts: add a method for building legacy bindings This will be useful for porting hh tasks to go because hh runs out of memory. This creates a new bindings package called `legacy`. There are no changes to the legacy contracts, so we do not need to worry about checking for changes. --- .../CanonicalTransactionChain.go | 1522 +++++++++++++++++ packages/contracts/package.json | 1 + packages/contracts/scripts/legacy-bindings.sh | 23 + 3 files changed, 1546 insertions(+) create mode 100644 op-bindings/legacy-bindings/CanonicalTransactionChain.go create mode 100755 packages/contracts/scripts/legacy-bindings.sh diff --git a/op-bindings/legacy-bindings/CanonicalTransactionChain.go b/op-bindings/legacy-bindings/CanonicalTransactionChain.go new file mode 100644 index 000000000..d9848e847 --- /dev/null +++ b/op-bindings/legacy-bindings/CanonicalTransactionChain.go @@ -0,0 +1,1522 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package legacy_bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "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/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// LibOVMCodecQueueElement is an auto generated low-level Go binding around an user-defined struct. +type LibOVMCodecQueueElement struct { + TransactionHash [32]byte + Timestamp *big.Int + BlockNumber *big.Int +} + +// CanonicalTransactionChainMetaData contains all meta data concerning the CanonicalTransactionChain contract. +var CanonicalTransactionChainMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_libAddressManager\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_maxTransactionGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_l2GasDiscountDivisor\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_enqueueGasCost\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"l2GasDiscountDivisor\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"enqueueGasCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"enqueueL2GasPrepaid\",\"type\":\"uint256\"}],\"name\":\"L2GasParamsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_startingQueueIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_numQueueElements\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_totalElements\",\"type\":\"uint256\"}],\"name\":\"QueueBatchAppended\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_startingQueueIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_numQueueElements\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_totalElements\",\"type\":\"uint256\"}],\"name\":\"SequencerBatchAppended\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_batchIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"_batchRoot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_batchSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_prevTotalElements\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"_extraData\",\"type\":\"bytes\"}],\"name\":\"TransactionBatchAppended\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_l1TxOrigin\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_queueIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_timestamp\",\"type\":\"uint256\"}],\"name\":\"TransactionEnqueued\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"MAX_ROLLUP_TX_SIZE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MIN_ROLLUP_TX_GAS\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"appendSequencerBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batches\",\"outputs\":[{\"internalType\":\"contractIChainStorageContainer\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"enqueue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"enqueueGasCost\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"enqueueL2GasPrepaid\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLastBlockNumber\",\"outputs\":[{\"internalType\":\"uint40\",\"name\":\"\",\"type\":\"uint40\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLastTimestamp\",\"outputs\":[{\"internalType\":\"uint40\",\"name\":\"\",\"type\":\"uint40\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNextQueueIndex\",\"outputs\":[{\"internalType\":\"uint40\",\"name\":\"\",\"type\":\"uint40\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNumPendingQueueElements\",\"outputs\":[{\"internalType\":\"uint40\",\"name\":\"\",\"type\":\"uint40\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_index\",\"type\":\"uint256\"}],\"name\":\"getQueueElement\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"transactionHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint40\",\"name\":\"timestamp\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"blockNumber\",\"type\":\"uint40\"}],\"internalType\":\"structLib_OVMCodec.QueueElement\",\"name\":\"_element\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getQueueLength\",\"outputs\":[{\"internalType\":\"uint40\",\"name\":\"\",\"type\":\"uint40\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTotalBatches\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_totalBatches\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTotalElements\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_totalElements\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l2GasDiscountDivisor\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"libAddressManager\",\"outputs\":[{\"internalType\":\"contractLib_AddressManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxTransactionGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"}],\"name\":\"resolve\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_l2GasDiscountDivisor\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_enqueueGasCost\",\"type\":\"uint256\"}],\"name\":\"setGasParams\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5060405162001a9838038062001a9883398101604081905261003191610072565b600080546001600160a01b0319166001600160a01b03861617905560048390556002829055600181905561006581836100bd565b600355506100ea92505050565b6000806000806080858703121561008857600080fd5b84516001600160a01b038116811461009f57600080fd5b60208601516040870151606090970151919890975090945092505050565b60008160001904831182151516156100e557634e487b7160e01b600052601160045260246000fd5b500290565b61199e80620000fa6000396000f3fe608060405234801561001057600080fd5b506004361061016c5760003560e01c8063876ed5cb116100cd578063d0f8934411610081578063e654b1fb11610066578063e654b1fb146102c0578063edcc4a45146102c9578063f722b41a146102dc57600080fd5b8063d0f89344146102b0578063e561dddc146102b857600080fd5b8063b8f77005116100b2578063b8f7700514610297578063ccf987c81461029f578063cfdf677e146102a857600080fd5b8063876ed5cb146102855780638d38c6c11461028e57600080fd5b80635ae6256d1161012457806378f4b2f21161010957806378f4b2f2146102645780637a167a8a1461026e5780637aa63a861461027d57600080fd5b80635ae6256d146102475780636fee07e01461024f57600080fd5b80632a7f18be116101555780632a7f18be146101d25780633789977014610216578063461a44781461023457600080fd5b80630b3dfa9714610171578063299ca4781461018d575b600080fd5b61017a60035481565b6040519081526020015b60405180910390f35b6000546101ad9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610184565b6101e56101e03660046113e5565b6102e4565b604080518251815260208084015164ffffffffff908116918301919091529282015190921690820152606001610184565b61021e610362565b60405164ffffffffff9091168152602001610184565b6101ad6102423660046114c1565b610376565b61021e610423565b61026261025d366004611537565b610437565b005b61017a620186a081565b60055464ffffffffff1661021e565b61017a610899565b61017a61c35081565b61017a60045481565b60065461021e565b61017a60025481565b6101ad6108b4565b6102626108dc565b61017a610df8565b61017a60015481565b6102626102d73660046115a4565b610e7f565b61021e611016565b604080516060810182526000808252602082018190529181019190915260068281548110610314576103146115c6565b6000918252602091829020604080516060810182526002909302909101805483526001015464ffffffffff808216948401949094526501000000000090049092169181019190915292915050565b60008061036d611032565b50949350505050565b600080546040517fbf40fac100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063bf40fac1906103cd908590600401611660565b60206040518083038186803b1580156103e557600080fd5b505afa1580156103f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041d919061167a565b92915050565b60008061042e611032565b95945050505050565b61c350815111156104cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f5472616e73616374696f6e20646174612073697a652065786365656473206d6160448201527f78696d756d20666f7220726f6c6c7570207472616e73616374696f6e2e00000060648201526084015b60405180910390fd5b600454821115610561576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f5472616e73616374696f6e20676173206c696d69742065786365656473206d6160448201527f78696d756d20666f7220726f6c6c7570207472616e73616374696f6e2e00000060648201526084016104c6565b620186a08210156105f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f5472616e73616374696f6e20676173206c696d697420746f6f206c6f7720746f60448201527f20656e71756575652e000000000000000000000000000000000000000000000060648201526084016104c6565b6003548211156106dc5760006002546003548461061191906116c6565b61061b91906116dd565b905060005a90508181116106b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e73756666696369656e742067617320666f72204c322072617465206c696d60448201527f6974696e67206275726e2e00000000000000000000000000000000000000000060648201526084016104c6565b60005b825a6106c090846116c6565b10156106d857806106d081611718565b9150506106b4565b5050505b6000333214156106ed575033610706565b5033731111000000000000000000000000000000001111015b60008185858560405160200161071f9493929190611751565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828252805160209182012060608401835280845264ffffffffff42811692850192835243811693850193845260068054600181810183556000838152975160029092027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f81019290925594517ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d4090910180549651841665010000000000027fffffffffffffffffffffffffffffffffffffffffffff0000000000000000000090971691909316179490941790559154919350610825916116c6565b9050808673ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f4b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb58888426040516108899392919061179a565b60405180910390a4505050505050565b6000806108a4611032565b50505064ffffffffff1692915050565b60006108d760405180606001604052806021815260200161194860219139610376565b905090565b60043560d81c60093560e890811c90600c35901c6108f8610899565b8364ffffffffff161461098d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603d60248201527f41637475616c20626174636820737461727420696e64657820646f6573206e6f60448201527f74206d6174636820657870656374656420737461727420696e6465782e00000060648201526084016104c6565b6109cb6040518060400160405280600d81526020017f4f564d5f53657175656e63657200000000000000000000000000000000000000815250610376565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f46756e6374696f6e2063616e206f6e6c792062652063616c6c6564206279207460448201527f68652053657175656e6365722e0000000000000000000000000000000000000060648201526084016104c6565b6000610a9762ffffff831660106117c3565b610aa290600f611800565b905064ffffffffff8116361015610b3b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4e6f7420656e6f756768204261746368436f6e74657874732070726f7669646560448201527f642e00000000000000000000000000000000000000000000000000000000000060648201526084016104c6565b6005546040805160808101825260008082526020820181905291810182905260608101829052909164ffffffffff169060005b8562ffffff168163ffffffff161015610bcc576000610b928263ffffffff166110ed565b8051909350839150610ba49086611818565b9450826020015184610bb69190611840565b9350508080610bc490611860565b915050610b6e565b5060065464ffffffffff83161115610c8c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f417474656d7074656420746f20617070656e64206d6f726520656c656d656e7460448201527f73207468616e2061726520617661696c61626c6520696e20746865207175657560648201527f652e000000000000000000000000000000000000000000000000000000000000608482015260a4016104c6565b6000610c9d8462ffffff8916611884565b63ffffffff169050600080836020015160001415610cc657505060408201516060830151610d37565b60006006610cd56001886118a9565b64ffffffffff1681548110610cec57610cec6115c6565b6000918252602091829020604080516060810182526002909302909101805483526001015464ffffffffff808216948401859052650100000000009091041691018190529093509150505b610d5b610d456001436116c6565b408a62ffffff168564ffffffffff168585611174565b7f602f1aeac0ca2e7a13e281a9ef0ad7838542712ce16780fa2ecffd351f05f899610d8684876118a9565b84610d8f610899565b6040805164ffffffffff94851681529390921660208401529082015260600160405180910390a15050600580547fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000001664ffffffffff949094169390931790925550505050505050565b6000610e026108b4565b73ffffffffffffffffffffffffffffffffffffffff16631f7b6d326040518163ffffffff1660e01b815260040160206040518083038186803b158015610e4757600080fd5b505afa158015610e5b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d791906118c7565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b158015610ee557600080fd5b505afa158015610ef9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f1d919061167a565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610fb1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f6e6c792063616c6c61626c6520627920746865204275726e2041646d696e2e60448201526064016104c6565b60018190556002829055610fc581836117c3565b60038190556002546001546040805192835260208301919091528101919091527fc6ed75e96b8b18b71edc1a6e82a9d677f8268c774a262c624eeb2cf0a8b3e07e9060600160405180910390a15050565b6005546006546000916108d79164ffffffffff909116906118a9565b60008060008060006110426108b4565b73ffffffffffffffffffffffffffffffffffffffff1663ccf8f9696040518163ffffffff1660e01b815260040160206040518083038186803b15801561108757600080fd5b505afa15801561109b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110bf91906118e0565b64ffffffffff602882901c811697605083901c82169750607883901c8216965060a09290921c169350915050565b6111186040518060800160405280600081526020016000815260200160008152602001600081525090565b60006111256010846117c3565b61113090600f611800565b60408051608081018252823560e890811c82526003840135901c6020820152600683013560d890811c92820192909252600b90920135901c60608201529392505050565b600061117e6108b4565b905060008061118b611032565b50509150915060006040518060a001604052808573ffffffffffffffffffffffffffffffffffffffff16631f7b6d326040518163ffffffff1660e01b815260040160206040518083038186803b1580156111e457600080fd5b505afa1580156111f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061121c91906118c7565b81526020018a81526020018981526020018464ffffffffff16815260200160405180602001604052806000815250815250905080600001517f127186556e7be68c7e31263195225b4de02820707889540969f62c05cf73525e82602001518360400151846060015185608001516040516112999493929190611922565b60405180910390a260006112ac8261139f565b905060006112e78360400151866112c39190611840565b6112cd8b87611840565b602890811b9190911760508b901b1760788a901b17901b90565b6040517f2015276c000000000000000000000000000000000000000000000000000000008152600481018490527fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000008216602482015290915073ffffffffffffffffffffffffffffffffffffffff871690632015276c90604401600060405180830381600087803b15801561137a57600080fd5b505af115801561138e573d6000803e3d6000fd5b505050505050505050505050505050565b600081602001518260400151836060015184608001516040516020016113c89493929190611922565b604051602081830303815290604052805190602001209050919050565b6000602082840312156113f757600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600067ffffffffffffffff80841115611448576114486113fe565b604051601f85017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561148e5761148e6113fe565b816040528093508581528686860111156114a757600080fd5b858560208301376000602087830101525050509392505050565b6000602082840312156114d357600080fd5b813567ffffffffffffffff8111156114ea57600080fd5b8201601f810184136114fb57600080fd5b61150a8482356020840161142d565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461153457600080fd5b50565b60008060006060848603121561154c57600080fd5b833561155781611512565b925060208401359150604084013567ffffffffffffffff81111561157a57600080fd5b8401601f8101861361158b57600080fd5b61159a8682356020840161142d565b9150509250925092565b600080604083850312156115b757600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000815180845260005b8181101561161b576020818501810151868301820152016115ff565b8181111561162d576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061167360208301846115f5565b9392505050565b60006020828403121561168c57600080fd5b815161167381611512565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000828210156116d8576116d8611697565b500390565b600082611713577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561174a5761174a611697565b5060010190565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152508360408301526080606083015261179060808301846115f5565b9695505050505050565b8381526060602082015260006117b360608301856115f5565b9050826040830152949350505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156117fb576117fb611697565b500290565b6000821982111561181357611813611697565b500190565b600063ffffffff80831681851680830382111561183757611837611697565b01949350505050565b600064ffffffffff80831681851680830382111561183757611837611697565b600063ffffffff8083168181141561187a5761187a611697565b6001019392505050565b600063ffffffff838116908316818110156118a1576118a1611697565b039392505050565b600064ffffffffff838116908316818110156118a1576118a1611697565b6000602082840312156118d957600080fd5b5051919050565b6000602082840312156118f257600080fd5b81517fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000008116811461167357600080fd5b84815283602082015282604082015260806060820152600061179060808301846115f556fe436861696e53746f72616765436f6e7461696e65722d4354432d62617463686573a264697066735822122071f9046c41835cfaa3b888bb4aa8b907bdd46588ad69741847a96bf3fcaad90264736f6c63430008090033", +} + +// CanonicalTransactionChainABI is the input ABI used to generate the binding from. +// Deprecated: Use CanonicalTransactionChainMetaData.ABI instead. +var CanonicalTransactionChainABI = CanonicalTransactionChainMetaData.ABI + +// CanonicalTransactionChainBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use CanonicalTransactionChainMetaData.Bin instead. +var CanonicalTransactionChainBin = CanonicalTransactionChainMetaData.Bin + +// DeployCanonicalTransactionChain deploys a new Ethereum contract, binding an instance of CanonicalTransactionChain to it. +func DeployCanonicalTransactionChain(auth *bind.TransactOpts, backend bind.ContractBackend, _libAddressManager common.Address, _maxTransactionGasLimit *big.Int, _l2GasDiscountDivisor *big.Int, _enqueueGasCost *big.Int) (common.Address, *types.Transaction, *CanonicalTransactionChain, error) { + parsed, err := CanonicalTransactionChainMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(CanonicalTransactionChainBin), backend, _libAddressManager, _maxTransactionGasLimit, _l2GasDiscountDivisor, _enqueueGasCost) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &CanonicalTransactionChain{CanonicalTransactionChainCaller: CanonicalTransactionChainCaller{contract: contract}, CanonicalTransactionChainTransactor: CanonicalTransactionChainTransactor{contract: contract}, CanonicalTransactionChainFilterer: CanonicalTransactionChainFilterer{contract: contract}}, nil +} + +// CanonicalTransactionChain is an auto generated Go binding around an Ethereum contract. +type CanonicalTransactionChain struct { + CanonicalTransactionChainCaller // Read-only binding to the contract + CanonicalTransactionChainTransactor // Write-only binding to the contract + CanonicalTransactionChainFilterer // Log filterer for contract events +} + +// CanonicalTransactionChainCaller is an auto generated read-only Go binding around an Ethereum contract. +type CanonicalTransactionChainCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// CanonicalTransactionChainTransactor is an auto generated write-only Go binding around an Ethereum contract. +type CanonicalTransactionChainTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// CanonicalTransactionChainFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type CanonicalTransactionChainFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// CanonicalTransactionChainSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type CanonicalTransactionChainSession struct { + Contract *CanonicalTransactionChain // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// CanonicalTransactionChainCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type CanonicalTransactionChainCallerSession struct { + Contract *CanonicalTransactionChainCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// CanonicalTransactionChainTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type CanonicalTransactionChainTransactorSession struct { + Contract *CanonicalTransactionChainTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// CanonicalTransactionChainRaw is an auto generated low-level Go binding around an Ethereum contract. +type CanonicalTransactionChainRaw struct { + Contract *CanonicalTransactionChain // Generic contract binding to access the raw methods on +} + +// CanonicalTransactionChainCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type CanonicalTransactionChainCallerRaw struct { + Contract *CanonicalTransactionChainCaller // Generic read-only contract binding to access the raw methods on +} + +// CanonicalTransactionChainTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type CanonicalTransactionChainTransactorRaw struct { + Contract *CanonicalTransactionChainTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewCanonicalTransactionChain creates a new instance of CanonicalTransactionChain, bound to a specific deployed contract. +func NewCanonicalTransactionChain(address common.Address, backend bind.ContractBackend) (*CanonicalTransactionChain, error) { + contract, err := bindCanonicalTransactionChain(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &CanonicalTransactionChain{CanonicalTransactionChainCaller: CanonicalTransactionChainCaller{contract: contract}, CanonicalTransactionChainTransactor: CanonicalTransactionChainTransactor{contract: contract}, CanonicalTransactionChainFilterer: CanonicalTransactionChainFilterer{contract: contract}}, nil +} + +// NewCanonicalTransactionChainCaller creates a new read-only instance of CanonicalTransactionChain, bound to a specific deployed contract. +func NewCanonicalTransactionChainCaller(address common.Address, caller bind.ContractCaller) (*CanonicalTransactionChainCaller, error) { + contract, err := bindCanonicalTransactionChain(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &CanonicalTransactionChainCaller{contract: contract}, nil +} + +// NewCanonicalTransactionChainTransactor creates a new write-only instance of CanonicalTransactionChain, bound to a specific deployed contract. +func NewCanonicalTransactionChainTransactor(address common.Address, transactor bind.ContractTransactor) (*CanonicalTransactionChainTransactor, error) { + contract, err := bindCanonicalTransactionChain(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &CanonicalTransactionChainTransactor{contract: contract}, nil +} + +// NewCanonicalTransactionChainFilterer creates a new log filterer instance of CanonicalTransactionChain, bound to a specific deployed contract. +func NewCanonicalTransactionChainFilterer(address common.Address, filterer bind.ContractFilterer) (*CanonicalTransactionChainFilterer, error) { + contract, err := bindCanonicalTransactionChain(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &CanonicalTransactionChainFilterer{contract: contract}, nil +} + +// bindCanonicalTransactionChain binds a generic wrapper to an already deployed contract. +func bindCanonicalTransactionChain(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(CanonicalTransactionChainABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_CanonicalTransactionChain *CanonicalTransactionChainRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _CanonicalTransactionChain.Contract.CanonicalTransactionChainCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_CanonicalTransactionChain *CanonicalTransactionChainRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.CanonicalTransactionChainTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_CanonicalTransactionChain *CanonicalTransactionChainRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.CanonicalTransactionChainTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _CanonicalTransactionChain.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.contract.Transact(opts, method, params...) +} + +// MAXROLLUPTXSIZE is a free data retrieval call binding the contract method 0x876ed5cb. +// +// Solidity: function MAX_ROLLUP_TX_SIZE() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) MAXROLLUPTXSIZE(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "MAX_ROLLUP_TX_SIZE") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// MAXROLLUPTXSIZE is a free data retrieval call binding the contract method 0x876ed5cb. +// +// Solidity: function MAX_ROLLUP_TX_SIZE() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) MAXROLLUPTXSIZE() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.MAXROLLUPTXSIZE(&_CanonicalTransactionChain.CallOpts) +} + +// MAXROLLUPTXSIZE is a free data retrieval call binding the contract method 0x876ed5cb. +// +// Solidity: function MAX_ROLLUP_TX_SIZE() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) MAXROLLUPTXSIZE() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.MAXROLLUPTXSIZE(&_CanonicalTransactionChain.CallOpts) +} + +// MINROLLUPTXGAS is a free data retrieval call binding the contract method 0x78f4b2f2. +// +// Solidity: function MIN_ROLLUP_TX_GAS() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) MINROLLUPTXGAS(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "MIN_ROLLUP_TX_GAS") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// MINROLLUPTXGAS is a free data retrieval call binding the contract method 0x78f4b2f2. +// +// Solidity: function MIN_ROLLUP_TX_GAS() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) MINROLLUPTXGAS() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.MINROLLUPTXGAS(&_CanonicalTransactionChain.CallOpts) +} + +// MINROLLUPTXGAS is a free data retrieval call binding the contract method 0x78f4b2f2. +// +// Solidity: function MIN_ROLLUP_TX_GAS() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) MINROLLUPTXGAS() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.MINROLLUPTXGAS(&_CanonicalTransactionChain.CallOpts) +} + +// Batches is a free data retrieval call binding the contract method 0xcfdf677e. +// +// Solidity: function batches() view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) Batches(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "batches") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Batches is a free data retrieval call binding the contract method 0xcfdf677e. +// +// Solidity: function batches() view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) Batches() (common.Address, error) { + return _CanonicalTransactionChain.Contract.Batches(&_CanonicalTransactionChain.CallOpts) +} + +// Batches is a free data retrieval call binding the contract method 0xcfdf677e. +// +// Solidity: function batches() view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) Batches() (common.Address, error) { + return _CanonicalTransactionChain.Contract.Batches(&_CanonicalTransactionChain.CallOpts) +} + +// EnqueueGasCost is a free data retrieval call binding the contract method 0xe654b1fb. +// +// Solidity: function enqueueGasCost() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) EnqueueGasCost(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "enqueueGasCost") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// EnqueueGasCost is a free data retrieval call binding the contract method 0xe654b1fb. +// +// Solidity: function enqueueGasCost() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) EnqueueGasCost() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.EnqueueGasCost(&_CanonicalTransactionChain.CallOpts) +} + +// EnqueueGasCost is a free data retrieval call binding the contract method 0xe654b1fb. +// +// Solidity: function enqueueGasCost() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) EnqueueGasCost() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.EnqueueGasCost(&_CanonicalTransactionChain.CallOpts) +} + +// EnqueueL2GasPrepaid is a free data retrieval call binding the contract method 0x0b3dfa97. +// +// Solidity: function enqueueL2GasPrepaid() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) EnqueueL2GasPrepaid(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "enqueueL2GasPrepaid") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// EnqueueL2GasPrepaid is a free data retrieval call binding the contract method 0x0b3dfa97. +// +// Solidity: function enqueueL2GasPrepaid() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) EnqueueL2GasPrepaid() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.EnqueueL2GasPrepaid(&_CanonicalTransactionChain.CallOpts) +} + +// EnqueueL2GasPrepaid is a free data retrieval call binding the contract method 0x0b3dfa97. +// +// Solidity: function enqueueL2GasPrepaid() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) EnqueueL2GasPrepaid() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.EnqueueL2GasPrepaid(&_CanonicalTransactionChain.CallOpts) +} + +// GetLastBlockNumber is a free data retrieval call binding the contract method 0x5ae6256d. +// +// Solidity: function getLastBlockNumber() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetLastBlockNumber(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getLastBlockNumber") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetLastBlockNumber is a free data retrieval call binding the contract method 0x5ae6256d. +// +// Solidity: function getLastBlockNumber() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetLastBlockNumber() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetLastBlockNumber(&_CanonicalTransactionChain.CallOpts) +} + +// GetLastBlockNumber is a free data retrieval call binding the contract method 0x5ae6256d. +// +// Solidity: function getLastBlockNumber() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetLastBlockNumber() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetLastBlockNumber(&_CanonicalTransactionChain.CallOpts) +} + +// GetLastTimestamp is a free data retrieval call binding the contract method 0x37899770. +// +// Solidity: function getLastTimestamp() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetLastTimestamp(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getLastTimestamp") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetLastTimestamp is a free data retrieval call binding the contract method 0x37899770. +// +// Solidity: function getLastTimestamp() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetLastTimestamp() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetLastTimestamp(&_CanonicalTransactionChain.CallOpts) +} + +// GetLastTimestamp is a free data retrieval call binding the contract method 0x37899770. +// +// Solidity: function getLastTimestamp() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetLastTimestamp() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetLastTimestamp(&_CanonicalTransactionChain.CallOpts) +} + +// GetNextQueueIndex is a free data retrieval call binding the contract method 0x7a167a8a. +// +// Solidity: function getNextQueueIndex() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetNextQueueIndex(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getNextQueueIndex") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetNextQueueIndex is a free data retrieval call binding the contract method 0x7a167a8a. +// +// Solidity: function getNextQueueIndex() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetNextQueueIndex() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetNextQueueIndex(&_CanonicalTransactionChain.CallOpts) +} + +// GetNextQueueIndex is a free data retrieval call binding the contract method 0x7a167a8a. +// +// Solidity: function getNextQueueIndex() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetNextQueueIndex() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetNextQueueIndex(&_CanonicalTransactionChain.CallOpts) +} + +// GetNumPendingQueueElements is a free data retrieval call binding the contract method 0xf722b41a. +// +// Solidity: function getNumPendingQueueElements() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetNumPendingQueueElements(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getNumPendingQueueElements") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetNumPendingQueueElements is a free data retrieval call binding the contract method 0xf722b41a. +// +// Solidity: function getNumPendingQueueElements() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetNumPendingQueueElements() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetNumPendingQueueElements(&_CanonicalTransactionChain.CallOpts) +} + +// GetNumPendingQueueElements is a free data retrieval call binding the contract method 0xf722b41a. +// +// Solidity: function getNumPendingQueueElements() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetNumPendingQueueElements() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetNumPendingQueueElements(&_CanonicalTransactionChain.CallOpts) +} + +// GetQueueElement is a free data retrieval call binding the contract method 0x2a7f18be. +// +// Solidity: function getQueueElement(uint256 _index) view returns((bytes32,uint40,uint40) _element) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetQueueElement(opts *bind.CallOpts, _index *big.Int) (LibOVMCodecQueueElement, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getQueueElement", _index) + + if err != nil { + return *new(LibOVMCodecQueueElement), err + } + + out0 := *abi.ConvertType(out[0], new(LibOVMCodecQueueElement)).(*LibOVMCodecQueueElement) + + return out0, err + +} + +// GetQueueElement is a free data retrieval call binding the contract method 0x2a7f18be. +// +// Solidity: function getQueueElement(uint256 _index) view returns((bytes32,uint40,uint40) _element) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetQueueElement(_index *big.Int) (LibOVMCodecQueueElement, error) { + return _CanonicalTransactionChain.Contract.GetQueueElement(&_CanonicalTransactionChain.CallOpts, _index) +} + +// GetQueueElement is a free data retrieval call binding the contract method 0x2a7f18be. +// +// Solidity: function getQueueElement(uint256 _index) view returns((bytes32,uint40,uint40) _element) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetQueueElement(_index *big.Int) (LibOVMCodecQueueElement, error) { + return _CanonicalTransactionChain.Contract.GetQueueElement(&_CanonicalTransactionChain.CallOpts, _index) +} + +// GetQueueLength is a free data retrieval call binding the contract method 0xb8f77005. +// +// Solidity: function getQueueLength() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetQueueLength(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getQueueLength") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetQueueLength is a free data retrieval call binding the contract method 0xb8f77005. +// +// Solidity: function getQueueLength() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetQueueLength() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetQueueLength(&_CanonicalTransactionChain.CallOpts) +} + +// GetQueueLength is a free data retrieval call binding the contract method 0xb8f77005. +// +// Solidity: function getQueueLength() view returns(uint40) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetQueueLength() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetQueueLength(&_CanonicalTransactionChain.CallOpts) +} + +// GetTotalBatches is a free data retrieval call binding the contract method 0xe561dddc. +// +// Solidity: function getTotalBatches() view returns(uint256 _totalBatches) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetTotalBatches(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getTotalBatches") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetTotalBatches is a free data retrieval call binding the contract method 0xe561dddc. +// +// Solidity: function getTotalBatches() view returns(uint256 _totalBatches) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetTotalBatches() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetTotalBatches(&_CanonicalTransactionChain.CallOpts) +} + +// GetTotalBatches is a free data retrieval call binding the contract method 0xe561dddc. +// +// Solidity: function getTotalBatches() view returns(uint256 _totalBatches) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetTotalBatches() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetTotalBatches(&_CanonicalTransactionChain.CallOpts) +} + +// GetTotalElements is a free data retrieval call binding the contract method 0x7aa63a86. +// +// Solidity: function getTotalElements() view returns(uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) GetTotalElements(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "getTotalElements") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetTotalElements is a free data retrieval call binding the contract method 0x7aa63a86. +// +// Solidity: function getTotalElements() view returns(uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) GetTotalElements() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetTotalElements(&_CanonicalTransactionChain.CallOpts) +} + +// GetTotalElements is a free data retrieval call binding the contract method 0x7aa63a86. +// +// Solidity: function getTotalElements() view returns(uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) GetTotalElements() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.GetTotalElements(&_CanonicalTransactionChain.CallOpts) +} + +// L2GasDiscountDivisor is a free data retrieval call binding the contract method 0xccf987c8. +// +// Solidity: function l2GasDiscountDivisor() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) L2GasDiscountDivisor(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "l2GasDiscountDivisor") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// L2GasDiscountDivisor is a free data retrieval call binding the contract method 0xccf987c8. +// +// Solidity: function l2GasDiscountDivisor() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) L2GasDiscountDivisor() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.L2GasDiscountDivisor(&_CanonicalTransactionChain.CallOpts) +} + +// L2GasDiscountDivisor is a free data retrieval call binding the contract method 0xccf987c8. +// +// Solidity: function l2GasDiscountDivisor() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) L2GasDiscountDivisor() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.L2GasDiscountDivisor(&_CanonicalTransactionChain.CallOpts) +} + +// LibAddressManager is a free data retrieval call binding the contract method 0x299ca478. +// +// Solidity: function libAddressManager() view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) LibAddressManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "libAddressManager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// LibAddressManager is a free data retrieval call binding the contract method 0x299ca478. +// +// Solidity: function libAddressManager() view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) LibAddressManager() (common.Address, error) { + return _CanonicalTransactionChain.Contract.LibAddressManager(&_CanonicalTransactionChain.CallOpts) +} + +// LibAddressManager is a free data retrieval call binding the contract method 0x299ca478. +// +// Solidity: function libAddressManager() view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) LibAddressManager() (common.Address, error) { + return _CanonicalTransactionChain.Contract.LibAddressManager(&_CanonicalTransactionChain.CallOpts) +} + +// MaxTransactionGasLimit is a free data retrieval call binding the contract method 0x8d38c6c1. +// +// Solidity: function maxTransactionGasLimit() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) MaxTransactionGasLimit(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "maxTransactionGasLimit") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// MaxTransactionGasLimit is a free data retrieval call binding the contract method 0x8d38c6c1. +// +// Solidity: function maxTransactionGasLimit() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) MaxTransactionGasLimit() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.MaxTransactionGasLimit(&_CanonicalTransactionChain.CallOpts) +} + +// MaxTransactionGasLimit is a free data retrieval call binding the contract method 0x8d38c6c1. +// +// Solidity: function maxTransactionGasLimit() view returns(uint256) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) MaxTransactionGasLimit() (*big.Int, error) { + return _CanonicalTransactionChain.Contract.MaxTransactionGasLimit(&_CanonicalTransactionChain.CallOpts) +} + +// Resolve is a free data retrieval call binding the contract method 0x461a4478. +// +// Solidity: function resolve(string _name) view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainCaller) Resolve(opts *bind.CallOpts, _name string) (common.Address, error) { + var out []interface{} + err := _CanonicalTransactionChain.contract.Call(opts, &out, "resolve", _name) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Resolve is a free data retrieval call binding the contract method 0x461a4478. +// +// Solidity: function resolve(string _name) view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) Resolve(_name string) (common.Address, error) { + return _CanonicalTransactionChain.Contract.Resolve(&_CanonicalTransactionChain.CallOpts, _name) +} + +// Resolve is a free data retrieval call binding the contract method 0x461a4478. +// +// Solidity: function resolve(string _name) view returns(address) +func (_CanonicalTransactionChain *CanonicalTransactionChainCallerSession) Resolve(_name string) (common.Address, error) { + return _CanonicalTransactionChain.Contract.Resolve(&_CanonicalTransactionChain.CallOpts, _name) +} + +// AppendSequencerBatch is a paid mutator transaction binding the contract method 0xd0f89344. +// +// Solidity: function appendSequencerBatch() returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactor) AppendSequencerBatch(opts *bind.TransactOpts) (*types.Transaction, error) { + return _CanonicalTransactionChain.contract.Transact(opts, "appendSequencerBatch") +} + +// AppendSequencerBatch is a paid mutator transaction binding the contract method 0xd0f89344. +// +// Solidity: function appendSequencerBatch() returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) AppendSequencerBatch() (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.AppendSequencerBatch(&_CanonicalTransactionChain.TransactOpts) +} + +// AppendSequencerBatch is a paid mutator transaction binding the contract method 0xd0f89344. +// +// Solidity: function appendSequencerBatch() returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactorSession) AppendSequencerBatch() (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.AppendSequencerBatch(&_CanonicalTransactionChain.TransactOpts) +} + +// Enqueue is a paid mutator transaction binding the contract method 0x6fee07e0. +// +// Solidity: function enqueue(address _target, uint256 _gasLimit, bytes _data) returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactor) Enqueue(opts *bind.TransactOpts, _target common.Address, _gasLimit *big.Int, _data []byte) (*types.Transaction, error) { + return _CanonicalTransactionChain.contract.Transact(opts, "enqueue", _target, _gasLimit, _data) +} + +// Enqueue is a paid mutator transaction binding the contract method 0x6fee07e0. +// +// Solidity: function enqueue(address _target, uint256 _gasLimit, bytes _data) returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) Enqueue(_target common.Address, _gasLimit *big.Int, _data []byte) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.Enqueue(&_CanonicalTransactionChain.TransactOpts, _target, _gasLimit, _data) +} + +// Enqueue is a paid mutator transaction binding the contract method 0x6fee07e0. +// +// Solidity: function enqueue(address _target, uint256 _gasLimit, bytes _data) returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactorSession) Enqueue(_target common.Address, _gasLimit *big.Int, _data []byte) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.Enqueue(&_CanonicalTransactionChain.TransactOpts, _target, _gasLimit, _data) +} + +// SetGasParams is a paid mutator transaction binding the contract method 0xedcc4a45. +// +// Solidity: function setGasParams(uint256 _l2GasDiscountDivisor, uint256 _enqueueGasCost) returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactor) SetGasParams(opts *bind.TransactOpts, _l2GasDiscountDivisor *big.Int, _enqueueGasCost *big.Int) (*types.Transaction, error) { + return _CanonicalTransactionChain.contract.Transact(opts, "setGasParams", _l2GasDiscountDivisor, _enqueueGasCost) +} + +// SetGasParams is a paid mutator transaction binding the contract method 0xedcc4a45. +// +// Solidity: function setGasParams(uint256 _l2GasDiscountDivisor, uint256 _enqueueGasCost) returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainSession) SetGasParams(_l2GasDiscountDivisor *big.Int, _enqueueGasCost *big.Int) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.SetGasParams(&_CanonicalTransactionChain.TransactOpts, _l2GasDiscountDivisor, _enqueueGasCost) +} + +// SetGasParams is a paid mutator transaction binding the contract method 0xedcc4a45. +// +// Solidity: function setGasParams(uint256 _l2GasDiscountDivisor, uint256 _enqueueGasCost) returns() +func (_CanonicalTransactionChain *CanonicalTransactionChainTransactorSession) SetGasParams(_l2GasDiscountDivisor *big.Int, _enqueueGasCost *big.Int) (*types.Transaction, error) { + return _CanonicalTransactionChain.Contract.SetGasParams(&_CanonicalTransactionChain.TransactOpts, _l2GasDiscountDivisor, _enqueueGasCost) +} + +// CanonicalTransactionChainL2GasParamsUpdatedIterator is returned from FilterL2GasParamsUpdated and is used to iterate over the raw logs and unpacked data for L2GasParamsUpdated events raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainL2GasParamsUpdatedIterator struct { + Event *CanonicalTransactionChainL2GasParamsUpdated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CanonicalTransactionChainL2GasParamsUpdatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainL2GasParamsUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainL2GasParamsUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CanonicalTransactionChainL2GasParamsUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CanonicalTransactionChainL2GasParamsUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CanonicalTransactionChainL2GasParamsUpdated represents a L2GasParamsUpdated event raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainL2GasParamsUpdated struct { + L2GasDiscountDivisor *big.Int + EnqueueGasCost *big.Int + EnqueueL2GasPrepaid *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterL2GasParamsUpdated is a free log retrieval operation binding the contract event 0xc6ed75e96b8b18b71edc1a6e82a9d677f8268c774a262c624eeb2cf0a8b3e07e. +// +// Solidity: event L2GasParamsUpdated(uint256 l2GasDiscountDivisor, uint256 enqueueGasCost, uint256 enqueueL2GasPrepaid) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) FilterL2GasParamsUpdated(opts *bind.FilterOpts) (*CanonicalTransactionChainL2GasParamsUpdatedIterator, error) { + + logs, sub, err := _CanonicalTransactionChain.contract.FilterLogs(opts, "L2GasParamsUpdated") + if err != nil { + return nil, err + } + return &CanonicalTransactionChainL2GasParamsUpdatedIterator{contract: _CanonicalTransactionChain.contract, event: "L2GasParamsUpdated", logs: logs, sub: sub}, nil +} + +// WatchL2GasParamsUpdated is a free log subscription operation binding the contract event 0xc6ed75e96b8b18b71edc1a6e82a9d677f8268c774a262c624eeb2cf0a8b3e07e. +// +// Solidity: event L2GasParamsUpdated(uint256 l2GasDiscountDivisor, uint256 enqueueGasCost, uint256 enqueueL2GasPrepaid) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) WatchL2GasParamsUpdated(opts *bind.WatchOpts, sink chan<- *CanonicalTransactionChainL2GasParamsUpdated) (event.Subscription, error) { + + logs, sub, err := _CanonicalTransactionChain.contract.WatchLogs(opts, "L2GasParamsUpdated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CanonicalTransactionChainL2GasParamsUpdated) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "L2GasParamsUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseL2GasParamsUpdated is a log parse operation binding the contract event 0xc6ed75e96b8b18b71edc1a6e82a9d677f8268c774a262c624eeb2cf0a8b3e07e. +// +// Solidity: event L2GasParamsUpdated(uint256 l2GasDiscountDivisor, uint256 enqueueGasCost, uint256 enqueueL2GasPrepaid) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) ParseL2GasParamsUpdated(log types.Log) (*CanonicalTransactionChainL2GasParamsUpdated, error) { + event := new(CanonicalTransactionChainL2GasParamsUpdated) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "L2GasParamsUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// CanonicalTransactionChainQueueBatchAppendedIterator is returned from FilterQueueBatchAppended and is used to iterate over the raw logs and unpacked data for QueueBatchAppended events raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainQueueBatchAppendedIterator struct { + Event *CanonicalTransactionChainQueueBatchAppended // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CanonicalTransactionChainQueueBatchAppendedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainQueueBatchAppended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainQueueBatchAppended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CanonicalTransactionChainQueueBatchAppendedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CanonicalTransactionChainQueueBatchAppendedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CanonicalTransactionChainQueueBatchAppended represents a QueueBatchAppended event raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainQueueBatchAppended struct { + StartingQueueIndex *big.Int + NumQueueElements *big.Int + TotalElements *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterQueueBatchAppended is a free log retrieval operation binding the contract event 0x64d7f508348c70dea42d5302a393987e4abc20e45954ab3f9d320207751956f0. +// +// Solidity: event QueueBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) FilterQueueBatchAppended(opts *bind.FilterOpts) (*CanonicalTransactionChainQueueBatchAppendedIterator, error) { + + logs, sub, err := _CanonicalTransactionChain.contract.FilterLogs(opts, "QueueBatchAppended") + if err != nil { + return nil, err + } + return &CanonicalTransactionChainQueueBatchAppendedIterator{contract: _CanonicalTransactionChain.contract, event: "QueueBatchAppended", logs: logs, sub: sub}, nil +} + +// WatchQueueBatchAppended is a free log subscription operation binding the contract event 0x64d7f508348c70dea42d5302a393987e4abc20e45954ab3f9d320207751956f0. +// +// Solidity: event QueueBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) WatchQueueBatchAppended(opts *bind.WatchOpts, sink chan<- *CanonicalTransactionChainQueueBatchAppended) (event.Subscription, error) { + + logs, sub, err := _CanonicalTransactionChain.contract.WatchLogs(opts, "QueueBatchAppended") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CanonicalTransactionChainQueueBatchAppended) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "QueueBatchAppended", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseQueueBatchAppended is a log parse operation binding the contract event 0x64d7f508348c70dea42d5302a393987e4abc20e45954ab3f9d320207751956f0. +// +// Solidity: event QueueBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) ParseQueueBatchAppended(log types.Log) (*CanonicalTransactionChainQueueBatchAppended, error) { + event := new(CanonicalTransactionChainQueueBatchAppended) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "QueueBatchAppended", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// CanonicalTransactionChainSequencerBatchAppendedIterator is returned from FilterSequencerBatchAppended and is used to iterate over the raw logs and unpacked data for SequencerBatchAppended events raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainSequencerBatchAppendedIterator struct { + Event *CanonicalTransactionChainSequencerBatchAppended // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CanonicalTransactionChainSequencerBatchAppendedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainSequencerBatchAppended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainSequencerBatchAppended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CanonicalTransactionChainSequencerBatchAppendedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CanonicalTransactionChainSequencerBatchAppendedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CanonicalTransactionChainSequencerBatchAppended represents a SequencerBatchAppended event raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainSequencerBatchAppended struct { + StartingQueueIndex *big.Int + NumQueueElements *big.Int + TotalElements *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSequencerBatchAppended is a free log retrieval operation binding the contract event 0x602f1aeac0ca2e7a13e281a9ef0ad7838542712ce16780fa2ecffd351f05f899. +// +// Solidity: event SequencerBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) FilterSequencerBatchAppended(opts *bind.FilterOpts) (*CanonicalTransactionChainSequencerBatchAppendedIterator, error) { + + logs, sub, err := _CanonicalTransactionChain.contract.FilterLogs(opts, "SequencerBatchAppended") + if err != nil { + return nil, err + } + return &CanonicalTransactionChainSequencerBatchAppendedIterator{contract: _CanonicalTransactionChain.contract, event: "SequencerBatchAppended", logs: logs, sub: sub}, nil +} + +// WatchSequencerBatchAppended is a free log subscription operation binding the contract event 0x602f1aeac0ca2e7a13e281a9ef0ad7838542712ce16780fa2ecffd351f05f899. +// +// Solidity: event SequencerBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) WatchSequencerBatchAppended(opts *bind.WatchOpts, sink chan<- *CanonicalTransactionChainSequencerBatchAppended) (event.Subscription, error) { + + logs, sub, err := _CanonicalTransactionChain.contract.WatchLogs(opts, "SequencerBatchAppended") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CanonicalTransactionChainSequencerBatchAppended) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "SequencerBatchAppended", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSequencerBatchAppended is a log parse operation binding the contract event 0x602f1aeac0ca2e7a13e281a9ef0ad7838542712ce16780fa2ecffd351f05f899. +// +// Solidity: event SequencerBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) ParseSequencerBatchAppended(log types.Log) (*CanonicalTransactionChainSequencerBatchAppended, error) { + event := new(CanonicalTransactionChainSequencerBatchAppended) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "SequencerBatchAppended", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// CanonicalTransactionChainTransactionBatchAppendedIterator is returned from FilterTransactionBatchAppended and is used to iterate over the raw logs and unpacked data for TransactionBatchAppended events raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainTransactionBatchAppendedIterator struct { + Event *CanonicalTransactionChainTransactionBatchAppended // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CanonicalTransactionChainTransactionBatchAppendedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainTransactionBatchAppended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainTransactionBatchAppended) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CanonicalTransactionChainTransactionBatchAppendedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CanonicalTransactionChainTransactionBatchAppendedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CanonicalTransactionChainTransactionBatchAppended represents a TransactionBatchAppended event raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainTransactionBatchAppended struct { + BatchIndex *big.Int + BatchRoot [32]byte + BatchSize *big.Int + PrevTotalElements *big.Int + ExtraData []byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransactionBatchAppended is a free log retrieval operation binding the contract event 0x127186556e7be68c7e31263195225b4de02820707889540969f62c05cf73525e. +// +// Solidity: event TransactionBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) FilterTransactionBatchAppended(opts *bind.FilterOpts, _batchIndex []*big.Int) (*CanonicalTransactionChainTransactionBatchAppendedIterator, error) { + + var _batchIndexRule []interface{} + for _, _batchIndexItem := range _batchIndex { + _batchIndexRule = append(_batchIndexRule, _batchIndexItem) + } + + logs, sub, err := _CanonicalTransactionChain.contract.FilterLogs(opts, "TransactionBatchAppended", _batchIndexRule) + if err != nil { + return nil, err + } + return &CanonicalTransactionChainTransactionBatchAppendedIterator{contract: _CanonicalTransactionChain.contract, event: "TransactionBatchAppended", logs: logs, sub: sub}, nil +} + +// WatchTransactionBatchAppended is a free log subscription operation binding the contract event 0x127186556e7be68c7e31263195225b4de02820707889540969f62c05cf73525e. +// +// Solidity: event TransactionBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) WatchTransactionBatchAppended(opts *bind.WatchOpts, sink chan<- *CanonicalTransactionChainTransactionBatchAppended, _batchIndex []*big.Int) (event.Subscription, error) { + + var _batchIndexRule []interface{} + for _, _batchIndexItem := range _batchIndex { + _batchIndexRule = append(_batchIndexRule, _batchIndexItem) + } + + logs, sub, err := _CanonicalTransactionChain.contract.WatchLogs(opts, "TransactionBatchAppended", _batchIndexRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CanonicalTransactionChainTransactionBatchAppended) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "TransactionBatchAppended", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTransactionBatchAppended is a log parse operation binding the contract event 0x127186556e7be68c7e31263195225b4de02820707889540969f62c05cf73525e. +// +// Solidity: event TransactionBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) ParseTransactionBatchAppended(log types.Log) (*CanonicalTransactionChainTransactionBatchAppended, error) { + event := new(CanonicalTransactionChainTransactionBatchAppended) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "TransactionBatchAppended", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// CanonicalTransactionChainTransactionEnqueuedIterator is returned from FilterTransactionEnqueued and is used to iterate over the raw logs and unpacked data for TransactionEnqueued events raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainTransactionEnqueuedIterator struct { + Event *CanonicalTransactionChainTransactionEnqueued // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CanonicalTransactionChainTransactionEnqueuedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainTransactionEnqueued) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CanonicalTransactionChainTransactionEnqueued) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CanonicalTransactionChainTransactionEnqueuedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CanonicalTransactionChainTransactionEnqueuedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CanonicalTransactionChainTransactionEnqueued represents a TransactionEnqueued event raised by the CanonicalTransactionChain contract. +type CanonicalTransactionChainTransactionEnqueued struct { + L1TxOrigin common.Address + Target common.Address + GasLimit *big.Int + Data []byte + QueueIndex *big.Int + Timestamp *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransactionEnqueued is a free log retrieval operation binding the contract event 0x4b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb5. +// +// Solidity: event TransactionEnqueued(address indexed _l1TxOrigin, address indexed _target, uint256 _gasLimit, bytes _data, uint256 indexed _queueIndex, uint256 _timestamp) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) FilterTransactionEnqueued(opts *bind.FilterOpts, _l1TxOrigin []common.Address, _target []common.Address, _queueIndex []*big.Int) (*CanonicalTransactionChainTransactionEnqueuedIterator, error) { + + var _l1TxOriginRule []interface{} + for _, _l1TxOriginItem := range _l1TxOrigin { + _l1TxOriginRule = append(_l1TxOriginRule, _l1TxOriginItem) + } + var _targetRule []interface{} + for _, _targetItem := range _target { + _targetRule = append(_targetRule, _targetItem) + } + + var _queueIndexRule []interface{} + for _, _queueIndexItem := range _queueIndex { + _queueIndexRule = append(_queueIndexRule, _queueIndexItem) + } + + logs, sub, err := _CanonicalTransactionChain.contract.FilterLogs(opts, "TransactionEnqueued", _l1TxOriginRule, _targetRule, _queueIndexRule) + if err != nil { + return nil, err + } + return &CanonicalTransactionChainTransactionEnqueuedIterator{contract: _CanonicalTransactionChain.contract, event: "TransactionEnqueued", logs: logs, sub: sub}, nil +} + +// WatchTransactionEnqueued is a free log subscription operation binding the contract event 0x4b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb5. +// +// Solidity: event TransactionEnqueued(address indexed _l1TxOrigin, address indexed _target, uint256 _gasLimit, bytes _data, uint256 indexed _queueIndex, uint256 _timestamp) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) WatchTransactionEnqueued(opts *bind.WatchOpts, sink chan<- *CanonicalTransactionChainTransactionEnqueued, _l1TxOrigin []common.Address, _target []common.Address, _queueIndex []*big.Int) (event.Subscription, error) { + + var _l1TxOriginRule []interface{} + for _, _l1TxOriginItem := range _l1TxOrigin { + _l1TxOriginRule = append(_l1TxOriginRule, _l1TxOriginItem) + } + var _targetRule []interface{} + for _, _targetItem := range _target { + _targetRule = append(_targetRule, _targetItem) + } + + var _queueIndexRule []interface{} + for _, _queueIndexItem := range _queueIndex { + _queueIndexRule = append(_queueIndexRule, _queueIndexItem) + } + + logs, sub, err := _CanonicalTransactionChain.contract.WatchLogs(opts, "TransactionEnqueued", _l1TxOriginRule, _targetRule, _queueIndexRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CanonicalTransactionChainTransactionEnqueued) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "TransactionEnqueued", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTransactionEnqueued is a log parse operation binding the contract event 0x4b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb5. +// +// Solidity: event TransactionEnqueued(address indexed _l1TxOrigin, address indexed _target, uint256 _gasLimit, bytes _data, uint256 indexed _queueIndex, uint256 _timestamp) +func (_CanonicalTransactionChain *CanonicalTransactionChainFilterer) ParseTransactionEnqueued(log types.Log) (*CanonicalTransactionChainTransactionEnqueued, error) { + event := new(CanonicalTransactionChainTransactionEnqueued) + if err := _CanonicalTransactionChain.contract.UnpackLog(event, "TransactionEnqueued", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 73a800e9d..facc73c37 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -22,6 +22,7 @@ "scripts": { "build": "yarn build:contracts && yarn copy:contracts && yarn autogen:artifacts && yarn build:typescript", "build:typescript": "tsc -p ./tsconfig.json", + "build:bindings": "./scripts/legacy-bindings.sh ./../../op-bindings/legacy-bindings", "build:contracts": "hardhat compile --show-stack-traces", "autogen:markdown": "ts-node scripts/generate-markdown.ts", "autogen:artifacts": "ts-node scripts/generate-artifacts.ts && ts-node scripts/generate-deployed-artifacts.ts", diff --git a/packages/contracts/scripts/legacy-bindings.sh b/packages/contracts/scripts/legacy-bindings.sh new file mode 100755 index 000000000..5de7b679a --- /dev/null +++ b/packages/contracts/scripts/legacy-bindings.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +OUTDIR="$1" + +if [ ! -d "$OUTDIR" ]; then + echo "Must pass output directory" + exit 1 +fi + +CONTRACTS=("CanonicalTransactionChain") +PKG=legacy_bindings + +for contract in ${CONTRACTS[@]}; do + TMPFILE=$(mktemp) + npx hardhat inspect $contract bytecode > "$TMPFILE" + ABI=$(npx hardhat inspect $contract abi) + + outfile="$OUTDIR/$contract.go" + + echo "$ABI" | abigen --abi - --pkg "$PKG" --bin "$TMPFILE" --type $contract --out "$outfile" + + rm "$TMPFILE" +done From 47d5f9bcd60fb1b7db2c827b7f14b3a0427f1bc5 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 21 Mar 2023 01:36:13 +0100 Subject: [PATCH 112/404] bedrock: update op-geth with v1.11.4 Vana changes --- go.mod | 10 ++-- go.sum | 180 ++------------------------------------------------------- 2 files changed, 9 insertions(+), 181 deletions(-) diff --git a/go.mod b/go.mod index 6ef566f31..52b7ab7d8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/docker/docker v20.10.21+incompatible github.com/docker/go-connections v0.4.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 - github.com/ethereum/go-ethereum v1.11.2 + github.com/ethereum/go-ethereum v1.11.4 github.com/fsnotify/fsnotify v1.6.0 github.com/golang/snappy v0.0.4 github.com/google/go-cmp v0.5.9 @@ -69,11 +69,11 @@ require ( github.com/francoispqt/gojay v1.2.13 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect - github.com/go-kit/kit v0.10.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/mock v1.6.0 // indirect @@ -86,7 +86,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect - github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.1.0 // indirect github.com/influxdata/influxdb v1.8.3 // indirect @@ -147,7 +146,6 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/prometheus/tsdb v0.10.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-18 v0.2.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.0 // indirect @@ -191,6 +189,6 @@ require ( nhooyr.io/websocket v1.8.7 // indirect ) -replace github.com/ethereum/go-ethereum v1.11.2 => github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b +replace github.com/ethereum/go-ethereum v1.11.4 => github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230321002540-11f0554a4313 -//replace github.com/ethereum/go-ethereum v1.11.2 => ../go-ethereum +//replace github.com/ethereum/go-ethereum v1.11.4 => ../go-ethereum diff --git a/go.sum b/go.sum index 3b630af30..b01f9061f 100644 --- a/go.sum +++ b/go.sum @@ -36,40 +36,24 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY= github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= @@ -78,7 +62,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -106,13 +89,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -120,10 +100,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= @@ -135,7 +113,6 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoG github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= @@ -143,18 +120,15 @@ github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHq github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -181,7 +155,6 @@ github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -193,14 +166,9 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 h1:kgvzE5wLsLa7XKfV85VZl40QXaMCaeFtHpPwJ8fhotY= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= @@ -209,7 +177,6 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/ github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= @@ -217,10 +184,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b h1:7RNzqCwam//7PPieblo8GSIVukwrfoPO+0xT1yMp9Zw= -github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230308025559-13ee9ab9153b/go.mod h1:/tjlXxOaovIyuF0l6+wCzr6AtDb3lYWTymmpQAQcqu8= +github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230321002540-11f0554a4313 h1:dBPc4CEzqmHUeU/Awk7Lw2mAaTc59T5W8CvAr+4YuzU= +github.com/ethereum-optimism/op-geth v1.11.2-de8c5df46.0.20230321002540-11f0554a4313/go.mod h1:SGLXBOtu2JlKrNoUG76EatI2uJX/WZRY4nmEyvE9Q38= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= @@ -230,8 +196,6 @@ github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwU github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -262,13 +226,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= @@ -283,7 +242,6 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= @@ -301,13 +259,13 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -318,7 +276,6 @@ github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -343,7 +300,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= @@ -376,7 +332,6 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -386,41 +341,22 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= @@ -428,18 +364,11 @@ github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:i github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw= -github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e/go.mod h1:j9cQbcqHQujT0oKJ38PylVfqohClLr3CvDC+Qcg+lhU= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.1.0 h1:gEe0Dp/lZmPZiDFzJJaOfUpOvv2MKUkoBX8lDrn9vKU= github.com/huin/goupnp v1.1.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= @@ -454,7 +383,6 @@ github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vA github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= @@ -495,12 +423,8 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -518,7 +442,6 @@ github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYb github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -553,6 +476,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -587,10 +511,7 @@ github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtI github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -605,7 +526,6 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -615,7 +535,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= @@ -629,7 +548,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= @@ -643,15 +561,9 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -699,21 +611,14 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -734,40 +639,28 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -776,41 +669,29 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= -github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= @@ -825,12 +706,10 @@ github.com/quic-go/webtransport-go v0.5.1 h1:1eVb7WDWCRoaeTtFHpFBJ6WDN1bSrPrRoW6 github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= @@ -842,13 +721,10 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/schollz/progressbar/v3 v3.13.0 h1:9TeeWRcjW2qd05I8Kf9knPkW4vLM/hYoa6z9ABvxje8= github.com/schollz/progressbar/v3 v3.13.0/go.mod h1:ZBYnSuLAX2LU8P8UiKN/KgF2DY58AJC8yfVYLPC8Ly4= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -878,14 +754,11 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -896,14 +769,10 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -930,7 +799,6 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -939,8 +807,6 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -958,7 +824,6 @@ github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPyS github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -972,16 +837,11 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= @@ -993,15 +853,12 @@ go.uber.org/fx v1.19.1/go.mod h1:bGK+AEy7XUwTBkqCsK/vDyFF0JJOA6X5KWpNC0e6qTA= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -1010,7 +867,6 @@ go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1 golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1070,14 +926,11 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1089,7 +942,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1126,15 +978,11 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1143,7 +991,6 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1151,14 +998,12 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1220,7 +1065,6 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1252,7 +1096,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1279,7 +1122,6 @@ gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZ google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1304,7 +1146,6 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -1321,13 +1162,9 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1351,10 +1188,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1362,11 +1197,8 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1394,7 +1226,5 @@ nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= From 89e9a4cdcdca80390797c65d5d2bfd9d5efaf069 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 21 Mar 2023 10:47:08 +1000 Subject: [PATCH 113/404] bedrock: Update to go 1.19 --- go.mod | 2 +- op-batcher/Dockerfile | 2 +- op-chain-ops/Dockerfile | 2 +- op-exporter/Dockerfile | 2 +- op-node/Dockerfile | 2 +- op-proposer/Dockerfile | 2 +- op-wheel/Dockerfile | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 52b7ab7d8..1ee69c817 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ethereum-optimism/optimism -go 1.18 +go 1.19 require ( github.com/btcsuite/btcd v0.23.3 diff --git a/op-batcher/Dockerfile b/op-batcher/Dockerfile index a4accc687..9ab3a2f34 100644 --- a/op-batcher/Dockerfile +++ b/op-batcher/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.18.0-alpine3.15 as builder +FROM --platform=$BUILDPLATFORM golang:1.19.0-alpine3.15 as builder ARG VERSION=v0.0.0 diff --git a/op-chain-ops/Dockerfile b/op-chain-ops/Dockerfile index b59cd7f32..689e6cc49 100644 --- a/op-chain-ops/Dockerfile +++ b/op-chain-ops/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18.0-alpine3.15 as builder +FROM golang:1.19.0-alpine3.15 as builder RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash diff --git a/op-exporter/Dockerfile b/op-exporter/Dockerfile index 33586f7e8..1861a08bb 100644 --- a/op-exporter/Dockerfile +++ b/op-exporter/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18.0-alpine3.15 as builder +FROM golang:1.19.0-alpine3.15 as builder # build from root of repo COPY ./op-exporter /app diff --git a/op-node/Dockerfile b/op-node/Dockerfile index fda7cac09..9bc1600bf 100644 --- a/op-node/Dockerfile +++ b/op-node/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.18.0-alpine3.15 as builder +FROM --platform=$BUILDPLATFORM golang:1.19.0-alpine3.15 as builder ARG VERSION=v0.0.0 diff --git a/op-proposer/Dockerfile b/op-proposer/Dockerfile index 444f08629..368aad1e7 100644 --- a/op-proposer/Dockerfile +++ b/op-proposer/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.18.0-alpine3.15 as builder +FROM --platform=$BUILDPLATFORM golang:1.19.0-alpine3.15 as builder ARG VERSION=v0.0.0 diff --git a/op-wheel/Dockerfile b/op-wheel/Dockerfile index fb2d4f2a6..bdf7a19fb 100644 --- a/op-wheel/Dockerfile +++ b/op-wheel/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18.0-alpine3.15 as builder +FROM golang:1.19.0-alpine3.15 as builder RUN apk add --no-cache make gcc musl-dev linux-headers From d2000ba339ef4cc3b13c351eac988ccba2304b76 Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Mon, 20 Mar 2023 17:50:40 -0700 Subject: [PATCH 114/404] op-node: Fix logging flags iwht incorrect prefix --- op-node/flags/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 44f23ac3c..6954da927 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -14,10 +14,10 @@ import ( // Flags -const envVarPrefix = "OP_NODE_" +const envVarPrefix = "OP_NODE" func prefixEnvVar(name string) string { - return envVarPrefix + name + return envVarPrefix + "_" + name } var ( From 6d5857cb86f12943b1f40b8573627d67be35df9c Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 20:51:19 -0400 Subject: [PATCH 115/404] use configurable gas estimation --- op-batcher/batcher/config.go | 46 +++++++++++++++++++++++----------- op-batcher/batcher/driver.go | 34 +++++++++++++++++-------- op-batcher/flags/flags.go | 14 ++++++++++- op-service/txmgr/txmgr.go | 27 +++++++++++++++----- op-service/txmgr/txmgr_test.go | 9 +++++++ 5 files changed, 99 insertions(+), 31 deletions(-) diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index ebd434f3a..a5512d59e 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -33,6 +33,14 @@ type Config struct { TxManagerConfig txmgr.Config NetworkTimeout time.Duration + // TxManagerTimeout is the maximum amount of time + // the driver should wait for the [txmgr] to send a transaction. + TxManagerTimeout time.Duration + + // OfflineGasEstimation specifies whether the batcher should calculate + // gas estimations offline using the [core.IntrinsicGas] function. + OfflineGasEstimation bool + // RollupConfig is queried at startup Rollup *rollup.Config @@ -111,6 +119,14 @@ type CLIConfig struct { /* Optional Params */ + // TxManagerTimeout is the max amount of time to wait for the [txmgr]. + // This will default to: 10 * time.Minute. + TxManagerTimeout time.Duration + + // OfflineGasEstimation specifies whether the batcher should calculate + // gas estimations offline using the [core.IntrinsicGas] function. + OfflineGasEstimation bool + // MaxL1TxSize is the maximum size of a batch tx submitted to L1. MaxL1TxSize uint64 @@ -169,19 +185,21 @@ func NewConfig(ctx *cli.Context) CLIConfig { ResubmissionTimeout: ctx.GlobalDuration(flags.ResubmissionTimeoutFlag.Name), /* Optional Flags */ - MaxChannelDuration: ctx.GlobalUint64(flags.MaxChannelDurationFlag.Name), - MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeBytesFlag.Name), - TargetL1TxSize: ctx.GlobalUint64(flags.TargetL1TxSizeBytesFlag.Name), - TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name), - ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name), - Stopped: ctx.GlobalBool(flags.StoppedFlag.Name), - Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name), - SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name), - PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name), - RPCConfig: rpc.ReadCLIConfig(ctx), - LogConfig: oplog.ReadCLIConfig(ctx), - MetricsConfig: opmetrics.ReadCLIConfig(ctx), - PprofConfig: oppprof.ReadCLIConfig(ctx), - SignerConfig: opsigner.ReadCLIConfig(ctx), + OfflineGasEstimation: ctx.GlobalBool(flags.OfflineGasEstimationFlag.Name), + TxManagerTimeout: ctx.GlobalDuration(flags.TxManagerTimeoutFlag.Name), + MaxChannelDuration: ctx.GlobalUint64(flags.MaxChannelDurationFlag.Name), + MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeBytesFlag.Name), + TargetL1TxSize: ctx.GlobalUint64(flags.TargetL1TxSizeBytesFlag.Name), + TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name), + ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name), + Stopped: ctx.GlobalBool(flags.StoppedFlag.Name), + Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name), + SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name), + PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name), + RPCConfig: rpc.ReadCLIConfig(ctx), + LogConfig: oplog.ReadCLIConfig(ctx), + MetricsConfig: opmetrics.ReadCLIConfig(ctx), + PprofConfig: oppprof.ReadCLIConfig(ctx), + SignerConfig: opsigner.ReadCLIConfig(ctx), } } diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index ab4726f8d..ec655080d 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" ) @@ -83,13 +84,14 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri } batcherCfg := Config{ - L1Client: l1Client, - L2Client: l2Client, - RollupNode: rollupClient, - PollInterval: cfg.PollInterval, - TxManagerConfig: txManagerConfig, - From: fromAddress, - Rollup: rcfg, + L1Client: l1Client, + L2Client: l2Client, + RollupNode: rollupClient, + PollInterval: cfg.PollInterval, + TxManagerConfig: txManagerConfig, + TxManagerTimeout: cfg.TxManagerTimeout, + From: fromAddress, + Rollup: rcfg, Channel: ChannelConfig{ SeqWindowSize: rcfg.SeqWindowSize, ChannelTimeout: rcfg.ChannelTimeout, @@ -310,6 +312,7 @@ func (l *BatchSubmitter) loop() { l.log.Error("unable to get tx data", "err", err) break } + // Record TX Status if receipt, err := l.SendTransaction(l.ctx, txdata.Bytes()); err != nil { l.recordFailedTx(txdata.ID(), err) @@ -338,6 +341,17 @@ func (l *BatchSubmitter) loop() { // This is a blocking method. It should not be called concurrently. // TODO: where to put concurrent transaction handling logic. func (l *BatchSubmitter) SendTransaction(ctx context.Context, data []byte) (*types.Receipt, error) { + // Do the gas estimation offline if specified + var gas uint64 + if l.OfflineGasEstimation { + intrinsicGas, err := core.IntrinsicGas(data, nil, false, true, true, false) + if err != nil { + return nil, fmt.Errorf("failed to calculate intrinsic gas: %w", err) + } + gas = intrinsicGas + } + + // Create the transaction tx, err := l.txMgr.CraftTx(ctx, txmgr.TxCandidate{ Recipient: l.Rollup.BatchInboxAddress, TxData: data, @@ -345,14 +359,14 @@ func (l *BatchSubmitter) SendTransaction(ctx context.Context, data []byte) (*typ ChainID: l.Rollup.L1ChainID, // Explicit instantiation here so we can make a note that a gas // limit of 0 will cause the [txmgr] to estimate the gas limit. - GasLimit: 0, + GasLimit: gas, }) if err != nil { return nil, fmt.Errorf("failed to create tx: %w", err) } - // TODO: Select a timeout that makes sense here. - ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) + // Send the transaction through the txmgr + ctx, cancel := context.WithTimeout(ctx, l.TxManagerTimeout) defer cancel() if receipt, err := l.txMgr.Send(ctx, tx); err != nil { l.log.Warn("unable to publish tx", "err", err, "data_size", len(data)) diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index 10af91de1..bd173cb87 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -1,6 +1,8 @@ package flags import ( + "time" + "github.com/urfave/cli" "github.com/ethereum-optimism/optimism/op-batcher/rpc" @@ -74,7 +76,17 @@ var ( } /* Optional flags */ - + OfflineGasEstimationFlag = cli.BoolFlag{ + Name: "offline-gas-estimation", + Usage: "Whether to use offline gas estimation", + EnvVar: opservice.PrefixEnvVar(envVarPrefix, "OFFLINE_GAS_ESTIMATION"), + } + TxManagerTimeoutFlag = cli.DurationFlag{ + Name: "tx-manager-timeout", + Usage: "Maximum duration to wait for L1 transactions, including resubmissions", + Value: 10 * time.Minute, + EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TX_MANAGER_TIMEOUT"), + } MaxChannelDurationFlag = cli.Uint64Flag{ Name: "max-channel-duration", Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.", diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 715a3c755..9dd9b3c5b 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -8,8 +8,8 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -104,6 +104,9 @@ type ETHBackend interface { // NonceAt returns the account nonce of the given account. // The block number can be nil, in which case the nonce is taken from the latest known block. NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) + /// EstimateGas returns an estimate of the amount of gas needed to execute the given + /// transaction against the current pending block. + EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) } // SimpleTxManager is a implementation of TxManager that performs linear fee @@ -185,13 +188,25 @@ func (m *SimpleTxManager) CraftTx(ctx context.Context, candidate TxCandidate) (* Data: candidate.TxData, } - // Calculate the intrinsic gas for the transaction m.l.Info("creating tx", "to", rawTx.To, "from", candidate.From) - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) - if err != nil { - return nil, fmt.Errorf("failed to calculate intrinsic gas: %w", err) + + // If the gas limit is set, we can use that as the gas + if candidate.GasLimit != 0 { + rawTx.Gas = candidate.GasLimit + } else { + // Calculate the intrinsic gas for the transaction + gas, err := m.backend.EstimateGas(ctx, ethereum.CallMsg{ + From: candidate.From, + To: &candidate.Recipient, + GasFeeCap: gasFeeCap, + GasTipCap: gasFeeCap, + Data: rawTx.Data, + }) + if err != nil { + return nil, fmt.Errorf("failed to estimate gas: %w", err) + } + rawTx.Gas = gas } - rawTx.Gas = gas ctx, cancel = context.WithTimeout(ctx, m.Config.NetworkTimeout) defer cancel() diff --git a/op-service/txmgr/txmgr_test.go b/op-service/txmgr/txmgr_test.go index 909d535e2..b5533fd19 100644 --- a/op-service/txmgr/txmgr_test.go +++ b/op-service/txmgr/txmgr_test.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testutils" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -175,6 +176,10 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ }, nil } +func (b *mockBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + return b.g.basefee().Uint64(), nil +} + func (b *mockBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { tip, _ := b.g.sample() return tip, nil @@ -580,6 +585,10 @@ func (b *failingBackend) SuggestGasTipCap(_ context.Context) (*big.Int, error) { return b.gasTip, nil } +func (b *failingBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + return b.baseFee.Uint64(), nil +} + func (b *failingBackend) NonceAt(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) { return 0, errors.New("unimplemented") } From 6dc52517379a606c123b1adfb99d2805bbaaddaa Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 20:53:40 -0400 Subject: [PATCH 116/404] add the gas estimation to the doc comments :memo: --- op-service/txmgr/txmgr.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 9dd9b3c5b..374b8a975 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -165,6 +165,8 @@ func (m *SimpleTxManager) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap *b // CraftTx creates the signed transaction to the batchInboxAddress. // It queries L1 for the current fee market conditions as well as for the nonce. // NOTE: This method SHOULD NOT publish the resulting transaction. +// NOTE: If the [TxCandidate.GasLimit] is non-zero, it will be used as the transaction's gas. +// NOTE: Otherwise, the [SimpleTxManager] will query the specified backend for an estimate. func (m *SimpleTxManager) CraftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) { gasTipCap, gasFeeCap, err := m.calcGasTipAndFeeCap(ctx) if err != nil { From a22524471b161e8e252d3fc10abb09c2e830142e Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 21:00:50 -0400 Subject: [PATCH 117/404] ticket op-batcher concurrent transaction handling --- op-batcher/batcher/driver.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index ec655080d..cfc9477cd 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -339,9 +339,9 @@ func (l *BatchSubmitter) loop() { // SendTransaction creates & submits a transaction to the batch inbox address with the given `data`. // It currently uses the underlying `txmgr` to handle transaction sending & price management. // This is a blocking method. It should not be called concurrently. -// TODO: where to put concurrent transaction handling logic. func (l *BatchSubmitter) SendTransaction(ctx context.Context, data []byte) (*types.Receipt, error) { // Do the gas estimation offline if specified + // A value of 0 will cause the [txmgr] to estimate the gas limit. var gas uint64 if l.OfflineGasEstimation { intrinsicGas, err := core.IntrinsicGas(data, nil, false, true, true, false) @@ -357,9 +357,7 @@ func (l *BatchSubmitter) SendTransaction(ctx context.Context, data []byte) (*typ TxData: data, From: l.From, ChainID: l.Rollup.L1ChainID, - // Explicit instantiation here so we can make a note that a gas - // limit of 0 will cause the [txmgr] to estimate the gas limit. - GasLimit: gas, + GasLimit: gas, }) if err != nil { return nil, fmt.Errorf("failed to create tx: %w", err) From 3c6f434555aeac1e2becc16d85d24883b7debd03 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 21:50:04 -0400 Subject: [PATCH 118/404] op-batcher driver send tx tests :test_tube: --- op-batcher/batcher/driver_test.go | 80 +++++++++++++++++++++++++++ op-service/txmgr/mocks/TxManager.go | 84 +++++++++++++++++++++++++++++ op-service/txmgr/txmgr.go | 2 + 3 files changed, 166 insertions(+) create mode 100644 op-batcher/batcher/driver_test.go create mode 100644 op-service/txmgr/mocks/TxManager.go diff --git a/op-batcher/batcher/driver_test.go b/op-batcher/batcher/driver_test.go new file mode 100644 index 000000000..6b9efa693 --- /dev/null +++ b/op-batcher/batcher/driver_test.go @@ -0,0 +1,80 @@ +package batcher + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/testlog" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum-optimism/optimism/op-service/txmgr/mocks" +) + +// TestSendTransaction tests that the driver can send a transaction +// through the txmgr. +func TestSendTransaction(t *testing.T) { + log := testlog.Logger(t, log.LvlCrit) + txMgr := mocks.TxManager{} + batcherInboxAddress := common.HexToAddress("0x42000000000000000000000000000000000000ff") + chainID := big.NewInt(1) + sender := common.HexToAddress("0xdeadbeef") + bs := BatchSubmitter{ + Config: Config{ + log: log, + From: sender, + OfflineGasEstimation: false, + Rollup: &rollup.Config{ + L1ChainID: chainID, + BatchInboxAddress: batcherInboxAddress, + }, + }, + txMgr: &txMgr, + } + txData := []byte{0x00, 0x01, 0x02} + + gasTipCap := big.NewInt(136) + gasFeeCap := big.NewInt(137) + gas := uint64(1337) + + candidate := txmgr.TxCandidate{ + Recipient: batcherInboxAddress, + TxData: txData, + From: sender, + ChainID: chainID, + GasLimit: uint64(0), + } + + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainID, + Nonce: 0, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Gas: gas, + To: &batcherInboxAddress, + Data: txData, + }) + txHash := tx.Hash() + + expectedReceipt := types.Receipt{ + Type: 1, + PostState: []byte{}, + Status: uint64(1), + CumulativeGasUsed: gas, + TxHash: txHash, + GasUsed: gas, + } + + txMgr.On("CraftTx", mock.Anything, candidate).Return(tx, nil) + txMgr.On("Send", mock.Anything, tx).Return(&expectedReceipt, nil) + + receipt, err := bs.SendTransaction(context.Background(), tx.Data()) + require.NoError(t, err) + require.Equal(t, receipt, &expectedReceipt) +} diff --git a/op-service/txmgr/mocks/TxManager.go b/op-service/txmgr/mocks/TxManager.go new file mode 100644 index 000000000..377220b12 --- /dev/null +++ b/op-service/txmgr/mocks/TxManager.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr" + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// TxManager is an autogenerated mock type for the TxManager type +type TxManager struct { + mock.Mock +} + +// CraftTx provides a mock function with given fields: ctx, candidate +func (_m *TxManager) CraftTx(ctx context.Context, candidate txmgr.TxCandidate) (*types.Transaction, error) { + ret := _m.Called(ctx, candidate) + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, txmgr.TxCandidate) (*types.Transaction, error)); ok { + return rf(ctx, candidate) + } + if rf, ok := ret.Get(0).(func(context.Context, txmgr.TxCandidate) *types.Transaction); ok { + r0 = rf(ctx, candidate) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, txmgr.TxCandidate) error); ok { + r1 = rf(ctx, candidate) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Send provides a mock function with given fields: ctx, tx +func (_m *TxManager) Send(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) { + ret := _m.Called(ctx, tx) + + var r0 *types.Receipt + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) (*types.Receipt, error)); ok { + return rf(ctx, tx) + } + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) *types.Receipt); ok { + r0 = rf(ctx, tx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Receipt) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *types.Transaction) error); ok { + r1 = rf(ctx, tx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewTxManager interface { + mock.TestingT + Cleanup(func()) +} + +// NewTxManager creates a new instance of TxManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewTxManager(t mockConstructorTestingTNewTxManager) *TxManager { + mock := &TxManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 374b8a975..8bcf5867d 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -68,6 +68,8 @@ type Config struct { // TxManager is an interface that allows callers to reliably publish txs, // bumping the gas price if needed, and obtain the receipt of the resulting tx. +// +//go:generate mockery --name TxManager --output ./mocks type TxManager interface { // Send is used to publish a transaction with incrementally higher gas // prices until the transaction eventually confirms. This method blocks From f19edc9f746f5a7f92e1663a28f745cedc05871f Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 20 Mar 2023 20:00:13 -0600 Subject: [PATCH 119/404] op-chain-ops: Fix data race in error handling The error channel in the OVM_ETH migration is buffered. In rare cases, errors were written to this channel in such a way that they were not processed by the collector goroutine. This meant that errors were not being caught by the `lastErr != nil` check, and were instead triggering the total supply check below. This error reliably reproduces when running the new TestMigrateBalancesRandomMissing test. This test generates a random state, and randomly removes an address/allowance from the witness data. It runs this 100 times per test. We didn't catch this with the other random test because it doesn't exercise the error handling codepath. --- op-chain-ops/ether/migrate.go | 40 ++++++++----- op-chain-ops/ether/migrate_test.go | 95 ++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 42 deletions(-) diff --git a/op-chain-ops/ether/migrate.go b/op-chain-ops/ether/migrate.go index 51bba05f4..b01393fc5 100644 --- a/op-chain-ops/ether/migrate.go +++ b/op-chain-ops/ether/migrate.go @@ -109,7 +109,7 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm outCh := make(chan accountData) // Channel to receive errors from each iteration job. errCh := make(chan error, checkJobs) - // Channel to cancel all iteration jobs as well as the collector. + // Channel to cancel all iteration jobs. cancelCh := make(chan struct{}) // Define a worker function to iterate over each partition. @@ -244,16 +244,17 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm go worker(start, end) } - // Make a channel to make sure that the collector process completes. - collectorCloseCh := make(chan struct{}) + // Make a channel to track when collector process completes. + collectorClosedCh := make(chan struct{}) + + // Make a channel to cancel the collector process. + collectorCancelCh := make(chan struct{}) // Keep track of the last error seen. var lastErr error - // There are multiple ways that the cancel channel can be closed: - // - if we receive an error from the errCh - // - if the collector process completes - // To prevent panics, we wrap the close in a sync.Once. + // The cancel channel can be closed if any of the workers returns an error. + // We wrap the close in a sync.Once to ensure that it's only closed once. var cancelOnce sync.Once // Create a map of accounts we've seen so that we can filter out duplicates. @@ -268,7 +269,7 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm progress := util.ProgressLogger(1000, "Migrated OVM_ETH storage slot") go func() { defer func() { - collectorCloseCh <- struct{}{} + collectorClosedCh <- struct{}{} }() for { select { @@ -291,10 +292,20 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm seenAccounts[account.address] = true case err := <-errCh: cancelOnce.Do(func() { - lastErr = err close(cancelCh) + lastErr = err }) - case <-cancelCh: + case <-collectorCancelCh: + // Explicitly drain the error channel. Since the error channel is buffered, it's possible + // for the wg.Wait() call below to unblock and cancel this goroutine before the error gets + // processed by the case statement above. + for len(errCh) > 0 { + err := <-errCh + if lastErr == nil { + lastErr = err + } + } + return } } @@ -302,13 +313,10 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm // Wait for the workers to finish. wg.Wait() - // Close the cancel channel to signal the collector process to stop. - cancelOnce.Do(func() { - close(cancelCh) - }) - // Wait for the collector process to finish. - <-collectorCloseCh + // Close the collector, and wait for it to finish. + close(collectorCancelCh) + <-collectorClosedCh // If we saw an error, return it. if lastErr != nil { diff --git a/op-chain-ops/ether/migrate_test.go b/op-chain-ops/ether/migrate_test.go index 3cd6d0552..20cb9bf7b 100644 --- a/op-chain-ops/ether/migrate_test.go +++ b/op-chain-ops/ether/migrate_test.go @@ -228,44 +228,58 @@ func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Addre } } -// TestMigrateBalancesRandom tests that the pre-check balances function works +// TestMigrateBalancesRandomOK tests that the pre-check balances function works // with random addresses. This test makes sure that the partition logic doesn't -// miss anything. -func TestMigrateBalancesRandom(t *testing.T) { +// miss anything, and helps detect concurrency errors. +func TestMigrateBalancesRandomOK(t *testing.T) { for i := 0; i < 100; i++ { - addresses := make([]common.Address, 0) - stateBalances := make(map[common.Address]*big.Int) + addresses, stateBalances, allowances, stateAllowances, totalSupply := setupRandTest(t) - allowances := make([]*crossdomain.Allowance, 0) - stateAllowances := make(map[common.Address]common.Address) - - totalSupply := big.NewInt(0) + db, factory := makeLegacyETH(t, totalSupply, stateBalances, stateAllowances) + err := doMigration(db, factory, addresses, allowances, big.NewInt(0), false) + require.NoError(t, err) - for j := 0; j < rand.Intn(10000); j++ { - addr := randAddr(t) - addresses = append(addresses, addr) - stateBalances[addr] = big.NewInt(int64(rand.Intn(1_000_000))) - totalSupply = new(big.Int).Add(totalSupply, stateBalances[addr]) + for addr, expBal := range stateBalances { + actBal := db.GetBalance(addr) + require.EqualValues(t, expBal, actBal) } + } +} - for j := 0; j < rand.Intn(1000); j++ { - addr := randAddr(t) - to := randAddr(t) - allowances = append(allowances, &crossdomain.Allowance{ - From: addr, - To: to, - }) - stateAllowances[addr] = to +// TestMigrateBalancesRandomMissing tests that the pre-check balances function works +// with random addresses when some of them are missing. This helps make sure that the +// partition logic doesn't miss anything, and helps detect concurrency errors. +func TestMigrateBalancesRandomMissing(t *testing.T) { + for i := 0; i < 100; i++ { + addresses, stateBalances, allowances, stateAllowances, totalSupply := setupRandTest(t) + + if len(addresses) == 0 { + continue } + // Remove a random address from the list of witnesses + idx := rand.Intn(len(addresses)) + addresses = append(addresses[:idx], addresses[idx+1:]...) + db, factory := makeLegacyETH(t, totalSupply, stateBalances, stateAllowances) err := doMigration(db, factory, addresses, allowances, big.NewInt(0), false) - require.NoError(t, err) + require.ErrorContains(t, err, "unknown storage slot") + } - for addr, expBal := range stateBalances { - actBal := db.GetBalance(addr) - require.EqualValues(t, expBal, actBal) + for i := 0; i < 100; i++ { + addresses, stateBalances, allowances, stateAllowances, totalSupply := setupRandTest(t) + + if len(allowances) == 0 { + continue } + + // Remove a random allowance from the list of witnesses + idx := rand.Intn(len(allowances)) + allowances = append(allowances[:idx], allowances[idx+1:]...) + + db, factory := makeLegacyETH(t, totalSupply, stateBalances, stateAllowances) + err := doMigration(db, factory, addresses, allowances, big.NewInt(0), false) + require.ErrorContains(t, err, "unknown storage slot") } } @@ -354,3 +368,32 @@ func randAddr(t *testing.T) common.Address { require.NoError(t, err) return addr } + +func setupRandTest(t *testing.T) ([]common.Address, map[common.Address]*big.Int, []*crossdomain.Allowance, map[common.Address]common.Address, *big.Int) { + addresses := make([]common.Address, 0) + stateBalances := make(map[common.Address]*big.Int) + + allowances := make([]*crossdomain.Allowance, 0) + stateAllowances := make(map[common.Address]common.Address) + + totalSupply := big.NewInt(0) + + for j := 0; j < rand.Intn(10000); j++ { + addr := randAddr(t) + addresses = append(addresses, addr) + stateBalances[addr] = big.NewInt(int64(rand.Intn(1_000_000))) + totalSupply = new(big.Int).Add(totalSupply, stateBalances[addr]) + } + + for j := 0; j < rand.Intn(1000); j++ { + addr := randAddr(t) + to := randAddr(t) + allowances = append(allowances, &crossdomain.Allowance{ + From: addr, + To: to, + }) + stateAllowances[addr] = to + } + + return addresses, stateBalances, allowances, stateAllowances, totalSupply +} From be331568984ae7d09da53527750a418298f52a2d Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Mon, 20 Mar 2023 16:04:58 -0400 Subject: [PATCH 120/404] fix(sdk): automatically create std/ETH bridges Updates the SDK to automatically create the Standard and ETH bridges as long as the L1StandardBridge provider is supplied. --- .changeset/cuddly-turkeys-burn.md | 5 +++++ packages/sdk/src/utils/contracts.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/cuddly-turkeys-burn.md diff --git a/.changeset/cuddly-turkeys-burn.md b/.changeset/cuddly-turkeys-burn.md new file mode 100644 index 000000000..011183959 --- /dev/null +++ b/.changeset/cuddly-turkeys-burn.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/sdk': patch +--- + +Have SDK automatically create Standard and ETH bridges when L1StandardBridge is provided. diff --git a/packages/sdk/src/utils/contracts.ts b/packages/sdk/src/utils/contracts.ts index 83882736b..877a3fe07 100644 --- a/packages/sdk/src/utils/contracts.ts +++ b/packages/sdk/src/utils/contracts.ts @@ -165,19 +165,19 @@ export const getBridgeAdapters = ( } ): BridgeAdapters => { const adapterData: BridgeAdapterData = { - ...(CONTRACT_ADDRESSES[l2ChainId] + ...(CONTRACT_ADDRESSES[l2ChainId] || opts?.contracts?.l1?.L1StandardBridge ? { Standard: { Adapter: StandardBridgeAdapter, l1Bridge: - opts.contracts?.l1?.L1StandardBridge || + opts?.contracts?.l1?.L1StandardBridge || CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge, l2Bridge: predeploys.L2StandardBridge, }, ETH: { Adapter: ETHBridgeAdapter, l1Bridge: - opts.contracts?.l1?.L1StandardBridge || + opts?.contracts?.l1?.L1StandardBridge || CONTRACT_ADDRESSES[l2ChainId].l1.L1StandardBridge, l2Bridge: predeploys.L2StandardBridge, }, From da79ef441f9c1451bb30a393c9461c8f74bf694d Mon Sep 17 00:00:00 2001 From: modagi Date: Tue, 21 Mar 2023 02:51:57 +0000 Subject: [PATCH 121/404] fix: fix flag name typo for MaxStateRootElements fix log package in proposer/driver --- .changeset/sharp-islands-attend.md | 6 ++++++ batch-submitter/config.go | 2 +- batch-submitter/drivers/proposer/driver.go | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/sharp-islands-attend.md diff --git a/.changeset/sharp-islands-attend.md b/.changeset/sharp-islands-attend.md new file mode 100644 index 000000000..c2d981c08 --- /dev/null +++ b/.changeset/sharp-islands-attend.md @@ -0,0 +1,6 @@ +--- +'@eth-optimism/batch-submitter-service': patch +--- + +fix flag name for MaxStateRootElements in batch-submitter +fix log package for proposer diff --git a/batch-submitter/config.go b/batch-submitter/config.go index 724e48949..30ceccf33 100644 --- a/batch-submitter/config.go +++ b/batch-submitter/config.go @@ -209,7 +209,7 @@ func NewConfig(ctx *cli.Context) (Config, error) { MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeFlag.Name), MaxPlaintextBatchSize: ctx.GlobalUint64(flags.MaxPlaintextBatchSizeFlag.Name), MinStateRootElements: ctx.GlobalUint64(flags.MinStateRootElementsFlag.Name), - MaxStateRootElements: ctx.GlobalUint64(flags.MinStateRootElementsFlag.Name), + MaxStateRootElements: ctx.GlobalUint64(flags.MaxStateRootElementsFlag.Name), MaxBatchSubmissionTime: ctx.GlobalDuration(flags.MaxBatchSubmissionTimeFlag.Name), PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name), diff --git a/batch-submitter/drivers/proposer/driver.go b/batch-submitter/drivers/proposer/driver.go index c096d88ad..8ddedb62e 100644 --- a/batch-submitter/drivers/proposer/driver.go +++ b/batch-submitter/drivers/proposer/driver.go @@ -13,13 +13,13 @@ import ( "github.com/ethereum-optimism/optimism/bss-core/metrics" "github.com/ethereum-optimism/optimism/bss-core/txmgr" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" - "github.com/ethereum-optimism/optimism/l2geth/log" "github.com/ethereum/go-ethereum/accounts/abi" "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/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" ) // stateRootSize is the size in bytes of a state root. From 0686effc1ab844e3b85a3fccd96e75ac940758c2 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 20 Mar 2023 21:15:31 -0600 Subject: [PATCH 122/404] op-chain-ops: Extract concurrent state iterator into util Pulls the concurrent state iterator into a re-usable library. Additional tests have been added to assert that the iterator touches every key in state at least once. This will allow us to perform a complete check of the OVM_ETH migration as the last step of the migration. --- op-chain-ops/ether/migrate.go | 320 ++++++----------------- op-chain-ops/ether/migrate_test.go | 84 +----- op-chain-ops/util/state_iterator.go | 161 ++++++++++++ op-chain-ops/util/state_iterator_test.go | 211 +++++++++++++++ 4 files changed, 457 insertions(+), 319 deletions(-) create mode 100644 op-chain-ops/util/state_iterator.go create mode 100644 op-chain-ops/util/state_iterator_test.go diff --git a/op-chain-ops/ether/migrate.go b/op-chain-ops/ether/migrate.go index b01393fc5..3a67f2740 100644 --- a/op-chain-ops/ether/migrate.go +++ b/op-chain-ops/ether/migrate.go @@ -3,10 +3,6 @@ package ether import ( "fmt" "math/big" - "sync" - - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/util" @@ -46,9 +42,6 @@ var ( common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000006"): true, } - // maxSlot is the maximum possible storage slot. - maxSlot = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - // sequencerEntrypointAddr is the address of the OVM sequencer entrypoint contract. sequencerEntrypointAddr = common.HexToAddress("0x4200000000000000000000000000000000000005") ) @@ -61,11 +54,9 @@ type accountData struct { address common.Address } -type DBFactory func() (*state.StateDB, error) - // MigrateBalances migrates all balances in the LegacyERC20ETH contract into state. It performs checks // in parallel with mutations in order to reduce overall migration time. -func MigrateBalances(mutableDB *state.StateDB, dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) error { +func MigrateBalances(mutableDB *state.StateDB, dbFactory util.DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) error { // Chain params to use for integrity checking. params := crossdomain.ParamsByChainID[chainID] if params == nil { @@ -75,7 +66,7 @@ func MigrateBalances(mutableDB *state.StateDB, dbFactory DBFactory, addresses [] return doMigration(mutableDB, dbFactory, addresses, allowances, params.ExpectedSupplyDelta, noCheck) } -func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, expDiff *big.Int, noCheck bool) error { +func doMigration(mutableDB *state.StateDB, dbFactory util.DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, expDiff *big.Int, noCheck bool) error { // We'll need to maintain a list of all addresses that we've seen along with all of the storage // slots based on the witness data. slotsAddrs := make(map[common.Hash]common.Address) @@ -103,226 +94,109 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm slotsAddrs[entrySK] = sequencerEntrypointAddr slotsInp[entrySK] = BalanceSlot - // WaitGroup to wait on each iteration job to finish. - var wg sync.WaitGroup // Channel to receive storage slot keys and values from each iteration job. outCh := make(chan accountData) - // Channel to receive errors from each iteration job. - errCh := make(chan error, checkJobs) - // Channel to cancel all iteration jobs. - cancelCh := make(chan struct{}) - - // Define a worker function to iterate over each partition. - worker := func(start, end common.Hash) { - // Decrement the WaitGroup when the function returns. - defer wg.Done() - - db, err := dbFactory() - if err != nil { - log.Crit("cannot get database", "err", err) - } - // Create a new storage trie. Each trie returned by db.StorageTrie - // is a copy, so this is safe for concurrent use. - st, err := db.StorageTrie(predeploys.LegacyERC20ETHAddr) - if err != nil { - // Should never happen, so explode if it does. - log.Crit("cannot get storage trie for LegacyERC20ETHAddr", "err", err) - } - if st == nil { - // Should never happen, so explode if it does. - log.Crit("nil storage trie for LegacyERC20ETHAddr") - } - - it := trie.NewIterator(st.NodeIterator(start.Bytes())) - - // Below code is largely based on db.ForEachStorage. We can't use that - // because it doesn't allow us to specify a start and end key. - for it.Next() { - select { - case <-cancelCh: - // If one of the workers encounters an error, cancel all of them. - return - default: - break - } + // Channel that gets closed when the collector is done. + doneCh := make(chan struct{}) - // Use the raw (i.e., secure hashed) key to check if we've reached - // the end of the partition. Use > rather than >= here to account for - // the fact that the values returned by PartitionKeys are inclusive. - // Duplicate addresses that may be returned by this iteration are - // filtered out in the collector. - if new(big.Int).SetBytes(it.Key).Cmp(end.Big()) > 0 { - return - } + // Create a map of accounts we've seen so that we can filter out duplicates. + seenAccounts := make(map[common.Address]bool) - // Skip if the value is empty. - rawValue := it.Value - if len(rawValue) == 0 { - continue - } + // Keep track of the total migrated supply. + totalFound := new(big.Int) - // Get the preimage. - rawKey := st.GetKey(it.Key) - if rawKey == nil { - // Should never happen, so explode if it does. - log.Crit("cannot get preimage for storage key", "key", it.Key) - } - key := common.BytesToHash(rawKey) + // Kick off a background process to collect + // values from the channel and add them to the map. + var count int + progress := util.ProgressLogger(1000, "Migrated OVM_ETH storage slot") + go func() { + defer func() { doneCh <- struct{}{} }() - // Parse the raw value. - _, content, _, err := rlp.Split(rawValue) - if err != nil { - // Should never happen, so explode if it does. - log.Crit("mal-formed data in state: %v", err) - } + for account := range outCh { + progress() - // We can safely ignore specific slots (totalSupply, name, symbol). - if ignoredSlots[key] { + // Filter out duplicate accounts. See the below note about keyspace iteration for + // why we may have to filter out duplicates. + if seenAccounts[account.address] { + log.Info("skipping duplicate account during iteration", "addr", account.address) continue } - slotType, ok := slotsInp[key] - if !ok { - if noCheck { - log.Error("ignoring unknown storage slot in state", "slot", key.String()) - } else { - errCh <- fmt.Errorf("unknown storage slot in state: %s", key.String()) - return - } - } - - // No accounts should have a balance in state. If they do, bail. - addr, ok := slotsAddrs[key] - if !ok { - log.Crit("could not find address in map - should never happen") - } - bal := db.GetBalance(addr) - if bal.Sign() != 0 { - log.Error( - "account has non-zero balance in state - should never happen", - "addr", addr, - "balance", bal.String(), - ) - if !noCheck { - errCh <- fmt.Errorf("account has non-zero balance in state - should never happen: %s", addr.String()) - return - } - } + // Accumulate addresses and total supply. + totalFound = new(big.Int).Add(totalFound, account.balance) - // Add balances to the total found. - switch slotType { - case BalanceSlot: - // Convert the value to a common.Hash, then send to the channel. - value := common.BytesToHash(content) - outCh <- accountData{ - balance: value.Big(), - legacySlot: key, - address: addr, - } - case AllowanceSlot: - // Allowance slot. - continue - default: - // Should never happen. - if noCheck { - log.Error("unknown slot type", "slot", key, "type", slotType) - } else { - log.Crit("unknown slot type %d, should never happen", slotType) - } - } + mutableDB.SetBalance(account.address, account.balance) + mutableDB.SetState(predeploys.LegacyERC20ETHAddr, account.legacySlot, common.Hash{}) + count++ + seenAccounts[account.address] = true } - } - - for i := 0; i < checkJobs; i++ { - wg.Add(1) - - // Partition the keyspace per worker. - start, end := PartitionKeyspace(i, checkJobs) - - // Kick off our worker. - go worker(start, end) - } - - // Make a channel to track when collector process completes. - collectorClosedCh := make(chan struct{}) - - // Make a channel to cancel the collector process. - collectorCancelCh := make(chan struct{}) - - // Keep track of the last error seen. - var lastErr error - - // The cancel channel can be closed if any of the workers returns an error. - // We wrap the close in a sync.Once to ensure that it's only closed once. - var cancelOnce sync.Once + }() - // Create a map of accounts we've seen so that we can filter out duplicates. - seenAccounts := make(map[common.Address]bool) + err := util.IterateState(dbFactory, predeploys.LegacyERC20ETHAddr, func(db *state.StateDB, key, value common.Hash) error { + // We can safely ignore specific slots (totalSupply, name, symbol). + if ignoredSlots[key] { + return nil + } - // Keep track of the total migrated supply. - totalFound := new(big.Int) + slotType, ok := slotsInp[key] + if !ok { + log.Error("unknown storage slot in state", "slot", key.String()) + if !noCheck { + return fmt.Errorf("unknown storage slot in state: %s", key.String()) + } + } - // Kick off another background process to collect - // values from the channel and add them to the map. - var count int - progress := util.ProgressLogger(1000, "Migrated OVM_ETH storage slot") - go func() { - defer func() { - collectorClosedCh <- struct{}{} - }() - for { - select { - case account := <-outCh: - progress() - - // Filter out duplicate accounts. See the below note about keyspace iteration for - // why we may have to filter out duplicates. - if seenAccounts[account.address] { - log.Info("skipping duplicate account during iteration", "addr", account.address) - continue - } - - // Accumulate addresses and total supply. - totalFound = new(big.Int).Add(totalFound, account.balance) - - mutableDB.SetBalance(account.address, account.balance) - mutableDB.SetState(predeploys.LegacyERC20ETHAddr, account.legacySlot, common.Hash{}) - count++ - seenAccounts[account.address] = true - case err := <-errCh: - cancelOnce.Do(func() { - close(cancelCh) - lastErr = err - }) - case <-collectorCancelCh: - // Explicitly drain the error channel. Since the error channel is buffered, it's possible - // for the wg.Wait() call below to unblock and cancel this goroutine before the error gets - // processed by the case statement above. - for len(errCh) > 0 { - err := <-errCh - if lastErr == nil { - lastErr = err - } - } - - return + // No accounts should have a balance in state. If they do, bail. + addr, ok := slotsAddrs[key] + if !ok { + log.Crit("could not find address in map - should never happen") + } + bal := db.GetBalance(addr) + if bal.Sign() != 0 { + log.Error( + "account has non-zero balance in state - should never happen", + "addr", addr, + "balance", bal.String(), + ) + if !noCheck { + return fmt.Errorf("account has non-zero balance in state - should never happen: %s", addr.String()) } } - }() - // Wait for the workers to finish. - wg.Wait() + // Add balances to the total found. + switch slotType { + case BalanceSlot: + // Send the data to the channel. + outCh <- accountData{ + balance: value.Big(), + legacySlot: key, + address: addr, + } + case AllowanceSlot: + // Allowance slot. Do nothing here. + default: + // Should never happen. + if noCheck { + log.Error("unknown slot type", "slot", key, "type", slotType) + } else { + log.Crit("unknown slot type %d, should never happen", slotType) + } + } - // Close the collector, and wait for it to finish. - close(collectorCancelCh) - <-collectorClosedCh + return nil + }, checkJobs) - // If we saw an error, return it. - if lastErr != nil { - return lastErr + if err != nil { + return err } + // Close the outCh to cancel the collector. The collector will signal that it's done + // using doneCh. Any values waiting to be read from outCh will be read before the + // collector exits. + close(outCh) + <-doneCh + // Log how many slots were iterated over. log.Info("Iterated legacy balances", "count", count) @@ -368,33 +242,3 @@ func doMigration(mutableDB *state.StateDB, dbFactory DBFactory, addresses []comm return nil } - -// PartitionKeyspace divides the key space into partitions by dividing the maximum keyspace -// by count then multiplying by i. This will leave some slots left over, which we handle below. It -// returns the start and end keys for the partition as a common.Hash. Note that the returned range -// of keys is inclusive, i.e., [start, end] NOT [start, end). -func PartitionKeyspace(i int, count int) (common.Hash, common.Hash) { - if i < 0 || count < 0 { - panic("i and count must be greater than 0") - } - - if i > count-1 { - panic("i must be less than count - 1") - } - - // Divide the key space into partitions by dividing the key space by the number - // of jobs. This will leave some slots left over, which we handle below. - partSize := new(big.Int).Div(maxSlot.Big(), big.NewInt(int64(count))) - - start := common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i)), partSize)) - var end common.Hash - if i < count-1 { - // If this is not the last partition, use the next partition's start key as the end. - end = common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i+1)), partSize)) - } else { - // If this is the last partition, use the max slot as the end. - end = maxSlot - } - - return start, end -} diff --git a/op-chain-ops/ether/migrate_test.go b/op-chain-ops/ether/migrate_test.go index 20cb9bf7b..3c588fdc7 100644 --- a/op-chain-ops/ether/migrate_test.go +++ b/op-chain-ops/ether/migrate_test.go @@ -1,11 +1,12 @@ package ether import ( - "fmt" "math/big" "math/rand" "testing" + "github.com/ethereum-optimism/optimism/op-chain-ops/util" + "github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" @@ -190,7 +191,7 @@ func TestMigrateBalances(t *testing.T) { } } -func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Address]*big.Int, allowances map[common.Address]common.Address) (*state.StateDB, DBFactory) { +func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Address]*big.Int, allowances map[common.Address]common.Address) (*state.StateDB, util.DBFactory) { memDB := rawdb.NewMemoryDatabase() db, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(memDB, &trie.Config{ Preimages: true, @@ -283,85 +284,6 @@ func TestMigrateBalancesRandomMissing(t *testing.T) { } } -func TestPartitionKeyspace(t *testing.T) { - tests := []struct { - i int - count int - expected [2]common.Hash - }{ - { - i: 0, - count: 1, - expected: [2]common.Hash{ - common.HexToHash("0x00"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - }, - }, - { - i: 0, - count: 2, - expected: [2]common.Hash{ - common.HexToHash("0x00"), - common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - }, - }, - { - i: 1, - count: 2, - expected: [2]common.Hash{ - common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - }, - }, - { - i: 0, - count: 3, - expected: [2]common.Hash{ - common.HexToHash("0x00"), - common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), - }, - }, - { - i: 1, - count: 3, - expected: [2]common.Hash{ - common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), - common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), - }, - }, - { - i: 2, - count: 3, - expected: [2]common.Hash{ - common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - }, - }, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("i %d, count %d", tt.i, tt.count), func(t *testing.T) { - start, end := PartitionKeyspace(tt.i, tt.count) - require.Equal(t, tt.expected[0], start) - require.Equal(t, tt.expected[1], end) - }) - } - - t.Run("panics on invalid i or count", func(t *testing.T) { - require.Panics(t, func() { - PartitionKeyspace(1, 1) - }) - require.Panics(t, func() { - PartitionKeyspace(-1, 1) - }) - require.Panics(t, func() { - PartitionKeyspace(0, -1) - }) - require.Panics(t, func() { - PartitionKeyspace(-1, -1) - }) - }) -} - func randAddr(t *testing.T) common.Address { var addr common.Address _, err := rand.Read(addr[:]) diff --git a/op-chain-ops/util/state_iterator.go b/op-chain-ops/util/state_iterator.go new file mode 100644 index 000000000..067809519 --- /dev/null +++ b/op-chain-ops/util/state_iterator.go @@ -0,0 +1,161 @@ +package util + +import ( + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + // maxSlot is the maximum possible storage slot. + maxSlot = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") +) + +type DBFactory func() (*state.StateDB, error) + +type StateCallback func(db *state.StateDB, key, value common.Hash) error + +func IterateState(dbFactory DBFactory, address common.Address, cb StateCallback, workers int) error { + if workers <= 0 { + panic("workers must be greater than 0") + } + + // WaitGroup to wait for all workers to finish. + var wg sync.WaitGroup + + // Channel to receive errors from each iteration job. + errCh := make(chan error, workers) + // Channel to cancel all iteration jobs. + cancelCh := make(chan struct{}) + + worker := func(start, end common.Hash) { + // Decrement the WaitGroup when the function returns. + defer wg.Done() + + db, err := dbFactory() + if err != nil { + // Should never happen, so explode if it does. + log.Crit("cannot create state db", "err", err) + } + st, err := db.StorageTrie(address) + if err != nil { + // Should never happen, so explode if it does. + log.Crit("cannot get storage trie", "address", address, "err", err) + } + // st can be nil if the account doesn't exist. + if st == nil { + errCh <- fmt.Errorf("account does not exist: %s", address.Hex()) + return + } + + it := trie.NewIterator(st.NodeIterator(start.Bytes())) + + // Below code is largely based on db.ForEachStorage. We can't use that + // because it doesn't allow us to specify a start and end key. + for it.Next() { + select { + case <-cancelCh: + // If one of the workers encounters an error, cancel all of them. + return + default: + break + } + + // Use the raw (i.e., secure hashed) key to check if we've reached + // the end of the partition. Use > rather than >= here to account for + // the fact that the values returned by PartitionKeys are inclusive. + // Duplicate addresses that may be returned by this iteration are + // filtered out in the collector. + if new(big.Int).SetBytes(it.Key).Cmp(end.Big()) > 0 { + return + } + + // Skip if the value is empty. + rawValue := it.Value + if len(rawValue) == 0 { + continue + } + + // Get the preimage. + rawKey := st.GetKey(it.Key) + if rawKey == nil { + // Should never happen, so explode if it does. + log.Crit("cannot get preimage for storage key", "key", it.Key) + } + key := common.BytesToHash(rawKey) + + // Parse the raw value. + _, content, _, err := rlp.Split(rawValue) + if err != nil { + // Should never happen, so explode if it does. + log.Crit("mal-formed data in state: %v", err) + } + + value := common.BytesToHash(content) + + // Call the callback with the DB, key, and value. Errors get + // bubbled up to the errCh. + if err := cb(db, key, value); err != nil { + errCh <- err + return + } + } + } + + for i := 0; i < workers; i++ { + wg.Add(1) + + // Partition the keyspace per worker. + start, end := PartitionKeyspace(i, workers) + + // Kick off our worker. + go worker(start, end) + } + + wg.Wait() + + for len(errCh) > 0 { + err := <-errCh + if err != nil { + return err + } + } + + return nil +} + +// PartitionKeyspace divides the key space into partitions by dividing the maximum keyspace +// by count then multiplying by i. This will leave some slots left over, which we handle below. It +// returns the start and end keys for the partition as a common.Hash. Note that the returned range +// of keys is inclusive, i.e., [start, end] NOT [start, end). +func PartitionKeyspace(i int, count int) (common.Hash, common.Hash) { + if i < 0 || count < 0 { + panic("i and count must be greater than 0") + } + + if i > count-1 { + panic("i must be less than count - 1") + } + + // Divide the key space into partitions by dividing the key space by the number + // of jobs. This will leave some slots left over, which we handle below. + partSize := new(big.Int).Div(maxSlot.Big(), big.NewInt(int64(count))) + + start := common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i)), partSize)) + var end common.Hash + if i < count-1 { + // If this is not the last partition, use the next partition's start key as the end. + end = common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i+1)), partSize)) + } else { + // If this is the last partition, use the max slot as the end. + end = maxSlot + } + + return start, end +} diff --git a/op-chain-ops/util/state_iterator_test.go b/op-chain-ops/util/state_iterator_test.go new file mode 100644 index 000000000..e4c3aed43 --- /dev/null +++ b/op-chain-ops/util/state_iterator_test.go @@ -0,0 +1,211 @@ +package util + +import ( + crand "crypto/rand" + "fmt" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/require" +) + +var testAddr = common.Address{0: 0xff} + +func TestStateIteratorWorkers(t *testing.T) { + _, factory, _ := setupRandTest(t) + + for i := -1; i <= 0; i++ { + require.Panics(t, func() { + _ = IterateState(factory, testAddr, func(db *state.StateDB, key, value common.Hash) error { + return nil + }, i) + }) + } +} + +func TestStateIteratorNonexistentAccount(t *testing.T) { + _, factory, _ := setupRandTest(t) + + require.ErrorContains(t, IterateState(factory, common.Address{}, func(db *state.StateDB, key, value common.Hash) error { + return nil + }, 1), "account does not exist") +} + +func TestStateIteratorRandomOK(t *testing.T) { + for i := 0; i < 100; i++ { + hashes, factory, workerCount := setupRandTest(t) + + seenHashes := make(map[common.Hash]bool) + hashCh := make(chan common.Hash) + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + for hash := range hashCh { + seenHashes[hash] = true + } + }() + + require.NoError(t, IterateState(factory, testAddr, func(db *state.StateDB, key, value common.Hash) error { + hashCh <- key + return nil + }, workerCount)) + + close(hashCh) + <-doneCh + + // Perform a less or equal check here in case of duplicates. The map check below will assert + // that all of the hashes are accounted for. + require.LessOrEqual(t, len(seenHashes), len(hashes)) + + // Every hash we put into state should have been iterated over. + for _, hash := range hashes { + require.Contains(t, seenHashes, hash) + } + } +} + +func TestStateIteratorRandomError(t *testing.T) { + for i := 0; i < 100; i++ { + hashes, factory, workerCount := setupRandTest(t) + + failHash := hashes[rand.Intn(len(hashes))] + require.ErrorContains(t, IterateState(factory, testAddr, func(db *state.StateDB, key, value common.Hash) error { + if key == failHash { + return fmt.Errorf("test error") + } + return nil + }, workerCount), "test error") + } +} + +func TestPartitionKeyspace(t *testing.T) { + tests := []struct { + i int + count int + expected [2]common.Hash + }{ + { + i: 0, + count: 1, + expected: [2]common.Hash{ + common.HexToHash("0x00"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + { + i: 0, + count: 2, + expected: [2]common.Hash{ + common.HexToHash("0x00"), + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + { + i: 1, + count: 2, + expected: [2]common.Hash{ + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + { + i: 0, + count: 3, + expected: [2]common.Hash{ + common.HexToHash("0x00"), + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + }, + }, + { + i: 1, + count: 3, + expected: [2]common.Hash{ + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + }, + }, + { + i: 2, + count: 3, + expected: [2]common.Hash{ + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("i %d, count %d", tt.i, tt.count), func(t *testing.T) { + start, end := PartitionKeyspace(tt.i, tt.count) + require.Equal(t, tt.expected[0], start) + require.Equal(t, tt.expected[1], end) + }) + } + + t.Run("panics on invalid i or count", func(t *testing.T) { + require.Panics(t, func() { + PartitionKeyspace(1, 1) + }) + require.Panics(t, func() { + PartitionKeyspace(-1, 1) + }) + require.Panics(t, func() { + PartitionKeyspace(0, -1) + }) + require.Panics(t, func() { + PartitionKeyspace(-1, -1) + }) + }) +} + +func setupRandTest(t *testing.T) ([]common.Hash, DBFactory, int) { + memDB := rawdb.NewMemoryDatabase() + db, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(memDB, &trie.Config{ + Preimages: true, + Cache: 1024, + }), nil) + require.NoError(t, err) + + hashCount := rand.Intn(100) + if hashCount == 0 { + hashCount = 1 + } + + hashes := make([]common.Hash, hashCount) + + db.CreateAccount(testAddr) + + for j := 0; j < hashCount; j++ { + hashes[j] = randHash(t) + db.SetState(testAddr, hashes[j], hashes[j]) + } + + root, err := db.Commit(false) + require.NoError(t, err) + + err = db.Database().TrieDB().Commit(root, true) + require.NoError(t, err) + + factory := func() (*state.StateDB, error) { + return state.New(root, state.NewDatabaseWithConfig(memDB, &trie.Config{ + Preimages: true, + Cache: 1024, + }), nil) + } + + workerCount := rand.Intn(64) + if workerCount == 0 { + workerCount = 1 + } + return hashes, factory, workerCount +} + +func randHash(t *testing.T) common.Hash { + var h common.Hash + _, err := crand.Read(h[:]) + require.NoError(t, err) + return h +} From 3fa459367da7d755c5d66e51958ee1f0be1b277c Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 21 Mar 2023 08:30:37 -0400 Subject: [PATCH 123/404] fixes and testing :gear: --- op-service/txmgr/txmgr.go | 2 +- op-service/txmgr/txmgr_test.go | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 8bcf5867d..fb58b0337 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -203,7 +203,7 @@ func (m *SimpleTxManager) CraftTx(ctx context.Context, candidate TxCandidate) (* From: candidate.From, To: &candidate.Recipient, GasFeeCap: gasFeeCap, - GasTipCap: gasFeeCap, + GasTipCap: gasTipCap, Data: rawTx.Data, }) if err != nil { diff --git a/op-service/txmgr/txmgr_test.go b/op-service/txmgr/txmgr_test.go index b5533fd19..00c7b9cbd 100644 --- a/op-service/txmgr/txmgr_test.go +++ b/op-service/txmgr/txmgr_test.go @@ -51,6 +51,20 @@ func newTestHarness(t *testing.T) *testHarness { return newTestHarnessWithConfig(t, configWithNumConfs(1)) } +// createTxCandidate creates a mock [TxCandidate]. +func (h testHarness) createTxCandidate() TxCandidate { + inbox := common.HexToAddress("0x42000000000000000000000000000000000000ff") + chainID := big.NewInt(1) + sender := common.HexToAddress("0xdeadbeef") + return TxCandidate{ + Recipient: inbox, + TxData: []byte{0x00, 0x01, 0x02}, + From: sender, + ChainID: chainID, + GasLimit: uint64(1337), + } +} + func configWithNumConfs(numConfirmations uint64) Config { return Config{ ResubmissionTimeout: time.Second, @@ -338,6 +352,51 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) { require.Nil(t, receipt) } +// TestTxMgr_CraftTx ensures that the tx manager will create transactions as expected. +func TestTxMgr_CraftTx(t *testing.T) { + t.Parallel() + h := newTestHarness(t) + candidate := h.createTxCandidate() + + // Craft the transaction. + gasTipCap, gasFeeCap := h.gasPricer.feesForEpoch(h.gasPricer.epoch + 1) + tx, err := h.mgr.CraftTx(context.Background(), candidate) + require.Nil(t, err) + require.NotNil(t, tx) + + // Validate the gas tip cap and fee cap. + require.Equal(t, gasTipCap, tx.GasTipCap()) + require.Equal(t, gasFeeCap, tx.GasFeeCap()) + + // Validate the nonce was set correctly using the backend. + require.Zero(t, tx.Nonce()) + + // Check that the gas was set using the gas limit. + require.Equal(t, candidate.GasLimit, tx.Gas()) +} + +// TestTxMgr_EstimateGas ensures that the tx manager will estimate +// the gas when candidate gas limit is zero in [CraftTx]. +func TestTxMgr_EstimateGas(t *testing.T) { + t.Parallel() + h := newTestHarness(t) + candidate := h.createTxCandidate() + + // Set the gas limit to zero to trigger gas estimation. + candidate.GasLimit = 0 + + // Gas estimate + gasEstimate := h.gasPricer.baseBaseFee.Uint64() + + // Craft the transaction. + tx, err := h.mgr.CraftTx(context.Background(), candidate) + require.Nil(t, err) + require.NotNil(t, tx) + + // Check that the gas was estimated correctly. + require.Equal(t, gasEstimate, tx.Gas()) +} + // TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a // receipt so long as at least one of the publications is able to succeed with a // simulated rpc failure. From c2379572def737f36ea060ef6df612c03e64b2a1 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 21 Mar 2023 10:50:22 -0400 Subject: [PATCH 124/404] fix nomenclature --- op-batcher/batcher/channel_builder_test.go | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 7fef5346f..03e8c4d9b 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -132,10 +132,10 @@ func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) { }) } -// FuzzDurationZero ensures that when whenever the MaxChannelDuration +// FuzzChannelBuilder_DurationZero ensures that when whenever the MaxChannelDuration // is not set to 0, the channel builder will always have a duration timeout // as long as the channel builder's timeout is set to 0. -func FuzzDurationZero(f *testing.F) { +func FuzzChannelBuilder_DurationZero(f *testing.F) { for i := range [10]int{} { f.Add(uint64(i), uint64(i)) } @@ -313,8 +313,8 @@ func FuzzSeqWindowZeroTimeoutClose(f *testing.F) { }) } -// TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame -func TestBuilderNextFrame(t *testing.T) { +// TestChannelBuilder_NextFrame tests calling NextFrame on a ChannelBuilder with only one frame +func TestChannelBuilder_NextFrame(t *testing.T) { channelConfig := defaultTestChannelConfig // Create a new channel builder @@ -353,8 +353,8 @@ func TestBuilderNextFrame(t *testing.T) { require.PanicsWithValue(t, "no next frame", func() { cb.NextFrame() }) } -// TestBuilderInvalidFrameId tests that a panic is thrown when a frame is pushed with an invalid frame id -func TestBuilderWrongFramePanic(t *testing.T) { +// TestChannelBuilder_OutputWrongFramePanic tests that a panic is thrown when a frame is pushed with an invalid frame id +func TestChannelBuilder_OutputWrongFramePanic(t *testing.T) { channelConfig := defaultTestChannelConfig // Construct a channel builder @@ -383,8 +383,8 @@ func TestBuilderWrongFramePanic(t *testing.T) { }) } -// TestOutputFramesHappy tests the OutputFrames function happy path -func TestOutputFramesHappy(t *testing.T) { +// TestChannelBuilder_OutputFramesWorks tests the [ChannelBuilder] OutputFrames is successful. +func TestChannelBuilder_OutputFramesWorks(t *testing.T) { channelConfig := defaultTestChannelConfig channelConfig.MaxFrameSize = 24 @@ -424,9 +424,9 @@ func TestOutputFramesHappy(t *testing.T) { } } -// TestMaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames] +// TestChannelBuilder_MaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames] // function errors when the max RLP bytes per channel is reached. -func TestMaxRLPBytesPerChannel(t *testing.T) { +func TestChannelBuilder_MaxRLPBytesPerChannel(t *testing.T) { t.Parallel() channelConfig := defaultTestChannelConfig channelConfig.MaxFrameSize = derive.MaxRLPBytesPerChannel * 2 @@ -442,9 +442,9 @@ func TestMaxRLPBytesPerChannel(t *testing.T) { require.ErrorIs(t, err, derive.ErrTooManyRLPBytes) } -// TestOutputFramesMaxFrameIndex tests the [channelBuilder.OutputFrames] +// TestChannelBuilder_OutputFramesMaxFrameIndex tests the [ChannelBuilder.OutputFrames] // function errors when the max frame index is reached. -func TestOutputFramesMaxFrameIndex(t *testing.T) { +func TestChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T) { channelConfig := defaultTestChannelConfig channelConfig.MaxFrameSize = 24 channelConfig.TargetNumFrames = math.MaxInt @@ -483,8 +483,8 @@ func TestOutputFramesMaxFrameIndex(t *testing.T) { } } -// TestBuilderAddBlock tests the AddBlock function -func TestBuilderAddBlock(t *testing.T) { +// TestChannelBuilder_AddBlock tests the AddBlock function +func TestChannelBuilder_AddBlock(t *testing.T) { channelConfig := defaultTestChannelConfig // Lower the max frame size so that we can batch @@ -514,8 +514,8 @@ func TestBuilderAddBlock(t *testing.T) { require.ErrorIs(t, addMiniBlock(cb), ErrInputTargetReached) } -// TestBuilderReset tests the Reset function -func TestBuilderReset(t *testing.T) { +// TestChannelBuilder_Reset tests the [Reset] function +func TestChannelBuilder_Reset(t *testing.T) { channelConfig := defaultTestChannelConfig // Lower the max frame size so that we can batch From 3c5822a81e0b757d41a75237a7bd9d73bad338c9 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Tue, 21 Mar 2023 11:26:32 -0500 Subject: [PATCH 125/404] feat(docs/op-stack): Add some troubleshooting Closing: DEVRL-779 --- .../src/docs/build/getting-started.md | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/docs/op-stack/src/docs/build/getting-started.md b/docs/op-stack/src/docs/build/getting-started.md index 46617b280..37d86a8e3 100644 --- a/docs/op-stack/src/docs/build/getting-started.md +++ b/docs/op-stack/src/docs/build/getting-started.md @@ -516,15 +516,33 @@ To use any other development stack, see the getting started tutorial, just repla ### Stopping your Rollup -To stop `op-geth` you should use Ctrl-C. +An orderly shutdown is done in the reverse order to the order in which components were started: -If `op-geth` aborts (for example, because the computer it is running on crashes), you will get these errors on `op-node`: +1. Stop `op-batcher`. +1. Stop `op-node`. +1. Stop `op-geth`. + + +### Starting your Rollup + +To restart the blockchain, use the same order of components you did when you initialized it. + +1. `op-geth` +1. `op-node` +1. `op-batcher` + + +### Errors + +#### Corrupt data directory + +If `op-geth` aborts (for example, because the computer it is running on crashes), you might get these errors on `op-node`: ``` WARN [02-16|21:22:02.868] Derivation process temporary error attempts=14 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000: failed to determine block-hash of hash 0x0000000000000000000000000000000000000000000000000000000000000000, could not get payload: not found" ``` -In that case, you need to remove `datadir`, reinitialize it: +This means that the data directory is corrupt and you need to reinitialize it: ```bash cd ~/op-geth @@ -536,17 +554,32 @@ echo "" > datadir/block-signer-key ./build/bin/geth init --datadir=./datadir ./genesis.json ``` -### Starting your Rollup +#### `op-geth` not synchronized yet -To restart the blockchain, use the same order of components you did when you initialized it. +You might get these errors when starting `op-node`: -1. `op-geth` -2. `op-node` -3. `op-batcher` +``` +t=2023-03-21T11:02:16-0500 lvl=info msg="Initializing Rollup Node" +ERROR[03-21|11:02:16.891] Unable to create the rollup node config error="failed to load p2p config: failed to load p2p discovery options: failed to open discovery db: resource temporarily unavailable" +CRIT [03-21|11:02:16.902] Application failed message="failed to load p2p config: failed to load p2p discovery options: failed to open discovery db: resource temporarily unavailable" +``` + +This usually means that `op-geth` is still synchronizing. +You will see these messages on its console: + +``` +INFO [03-21|11:01:01.296] Chain head was updated number=23650 hash=64d626..3762b7 root=b8f5cf..78bfc0 elapsed="69.897µs" age=8h36m41s +INFO [03-21|11:01:01.373] Starting work on payload id=0x76c8267ac16b0ec0 +INFO [03-21|11:01:01.374] Updated payload id=0x76c8267ac16b0ec0 number=23651 hash=e846d2..af628e txs=1 gas=46913 fees=0 root=85ef2b..33ce37 elapsed="297.264µs" +INFO [03-21|11:01:01.376] Stopping work on payload id=0x76c8267ac16b0ec0 reason=delivery +``` + +In this case wait to start `op-node` until `op-geth` is fully synchronized. ## Adding nodes -To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, similar to what you did for the first node: +To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, similar to what you did for the first node. +You should *not* add an `op-bathcer`, there should be only one. 1. Configure the OS and prerequisites as you did for the first node. 1. Build the Optimism monorepo and `op-geth` as you did for the first node. @@ -573,8 +606,9 @@ To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, simi ``` 1. Start `op-geth` (using the same command line you used on the initial node) -1. Start `op-node` (using the same command line you used on the initial node) 1. Wait while the node synchronizes +1. Start `op-node` (using the same command line you used on the initial node) + ## What’s next? From 25c7243b7eb897f81ce7affd695b1ceacb116763 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 12:08:49 -0400 Subject: [PATCH 126/404] fix batcher nits :hammer: --- op-batcher/Makefile | 1 + op-batcher/batcher/channel_builder.go | 12 ++- op-batcher/batcher/channel_builder_test.go | 95 +++++++++++++++++----- 3 files changed, 80 insertions(+), 28 deletions(-) diff --git a/op-batcher/Makefile b/op-batcher/Makefile index 3235b3b77..7ec5761e5 100644 --- a/op-batcher/Makefile +++ b/op-batcher/Makefile @@ -20,6 +20,7 @@ lint: golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" fuzz: + go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzChannelConfig_CheckTimeout ./batcher go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationZero ./batcher go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationTimeoutMaxChannelDuration ./batcher go test -run NOTAREALTEST -v -fuzztime 10s -fuzz FuzzDurationTimeoutZeroMaxChannelDuration ./batcher diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 77d7c0d85..9fdf0dfcb 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -12,8 +12,6 @@ import ( ) var ( - ErrZeroMaxFrameSize = errors.New("max frame size cannot be zero") - ErrSmallMaxFrameSize = errors.New("max frame size cannot be less than 23") ErrInvalidChannelTimeout = errors.New("channel timeout is less than the safety margin") ErrInputTargetReached = errors.New("target amount of input data reached") ErrMaxFrameIndex = errors.New("max frame index reached (uint16)") @@ -83,15 +81,15 @@ func (cc *ChannelConfig) Check() error { // will infinitely loop when trying to create frames in the // [channelBuilder.OutputFrames] function. if cc.MaxFrameSize == 0 { - return ErrZeroMaxFrameSize + return errors.New("max frame size cannot be zero") } - // If the [MaxFrameSize] is set to < 23, the channel out - // will underflow the maxSize variable in the [derive.ChannelOut]. + // If the [MaxFrameSize] is less than [FrameV0OverHeadSize], the channel + // out will underflow the maxSize variable in the [derive.ChannelOut]. // Since it is of type uint64, it will wrap around to a very large // number, making the frame size extremely large. - if cc.MaxFrameSize < 23 { - return ErrSmallMaxFrameSize + if cc.MaxFrameSize < derive.FrameV0OverHeadSize { + return fmt.Errorf("max frame size %d is less than the minimum 23", cc.MaxFrameSize) } return nil diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 03e8c4d9b..765396423 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -3,6 +3,7 @@ package batcher import ( "bytes" "errors" + "fmt" "math" "math/big" "math/rand" @@ -32,27 +33,79 @@ var defaultTestChannelConfig = ChannelConfig{ ApproxComprRatio: 0.4, } -// TestConfigValidation tests the validation of the [ChannelConfig] struct. -func TestConfigValidation(t *testing.T) { - // Construct a valid config. - validChannelConfig := defaultTestChannelConfig - require.NoError(t, validChannelConfig.Check()) - - // Set the config to have a zero max frame size. - validChannelConfig.MaxFrameSize = 0 - require.ErrorIs(t, validChannelConfig.Check(), ErrZeroMaxFrameSize) - - // Set the config to have a max frame size less than 23. - validChannelConfig.MaxFrameSize = 22 - require.ErrorIs(t, validChannelConfig.Check(), ErrSmallMaxFrameSize) - - // Reset the config and test the Timeout error. - // NOTE: We should be fuzzing these values with the constraint that - // SubSafetyMargin > ChannelTimeout to ensure validation. - validChannelConfig = defaultTestChannelConfig - validChannelConfig.ChannelTimeout = 0 - validChannelConfig.SubSafetyMargin = 1 - require.ErrorIs(t, validChannelConfig.Check(), ErrInvalidChannelTimeout) +// TestChannelConfig_Check tests the [ChannelConfig] [Check] function. +func TestChannelConfig_Check(t *testing.T) { + type test struct { + input ChannelConfig + assertion func(error) + } + + // Construct test cases that test the boundary conditions + zeroChannelConfig := defaultTestChannelConfig + zeroChannelConfig.MaxFrameSize = 0 + timeoutChannelConfig := defaultTestChannelConfig + timeoutChannelConfig.ChannelTimeout = 0 + timeoutChannelConfig.SubSafetyMargin = 1 + tests := []test{ + { + input: defaultTestChannelConfig, + assertion: func(output error) { + require.NoError(t, output) + }, + }, + { + input: timeoutChannelConfig, + assertion: func(output error) { + require.ErrorIs(t, output, ErrInvalidChannelTimeout) + }, + }, + { + input: zeroChannelConfig, + assertion: func(output error) { + require.EqualError(t, output, "max frame size cannot be zero") + }, + }, + } + for i := 1; i < derive.FrameV0OverHeadSize; i++ { + smallChannelConfig := defaultTestChannelConfig + smallChannelConfig.MaxFrameSize = uint64(i) + expectedErr := fmt.Sprintf("max frame size %d is less than the minimum 23", i) + tests = append(tests, test{ + input: smallChannelConfig, + assertion: func(output error) { + require.EqualError(t, output, expectedErr) + }, + }) + } + + // Run the table tests + for _, test := range tests { + test.assertion(test.input.Check()) + } +} + +// FuzzChannelConfig_CheckTimeout tests the [ChannelConfig] [Check] function +// with fuzzing to make sure that a [ErrInvalidChannelTimeout] is thrown when +// the [ChannelTimeout] is less than the [SubSafetyMargin]. +func FuzzChannelConfig_CheckTimeout(f *testing.F) { + for i := range [10]int{} { + f.Add(uint64(i+1), uint64(i)) + } + f.Fuzz(func(t *testing.T, channelTimeout uint64, subSafetyMargin uint64) { + // We only test where [ChannelTimeout] is less than the [SubSafetyMargin] + // So we cannot have [ChannelTimeout] be [math.MaxUint64] + if channelTimeout == math.MaxUint64 { + channelTimeout = math.MaxUint64 - 1 + } + if subSafetyMargin <= channelTimeout { + subSafetyMargin = channelTimeout + 1 + } + + channelConfig := defaultTestChannelConfig + channelConfig.ChannelTimeout = channelTimeout + channelConfig.SubSafetyMargin = subSafetyMargin + require.ErrorIs(t, channelConfig.Check(), ErrInvalidChannelTimeout) + }) } // addMiniBlock adds a minimal valid L2 block to the channel builder using the From 3c580424d27c137dbf35b6f0b8d57b3e23dbf62e Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Tue, 21 Mar 2023 14:16:10 -0400 Subject: [PATCH 127/404] fix(ci): run tests for common-ts Tests for common-ts were not being executed in CI because the command being triggered was "yarn test:coverage" and this command was a noop for common-ts. Also fixes a bug in the tests that wasn't caught by CI because of this. --- .circleci/config.yml | 4 ++++ packages/common-ts/.depcheckrc | 12 ++++++++++++ packages/common-ts/package.json | 5 ++--- packages/common-ts/test/metrics.spec.ts | 4 ++-- yarn.lock | 7 ------- 5 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 packages/common-ts/.depcheckrc diff --git a/.circleci/config.yml b/.circleci/config.yml index 706e5969a..36ebc6bee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -518,6 +518,10 @@ jobs: patterns: packages # Note: The below needs to be manually configured whenever we # add a new package to CI. + - run: + name: Check common-ts + command: npx depcheck + working_directory: packages/common-ts - run: name: Check contracts command: npx depcheck diff --git a/packages/common-ts/.depcheckrc b/packages/common-ts/.depcheckrc new file mode 100644 index 000000000..b1352d578 --- /dev/null +++ b/packages/common-ts/.depcheckrc @@ -0,0 +1,12 @@ +ignores: [ + "@babel/eslint-parser", + "@typescript-eslint/parser", + "eslint-plugin-import", + "eslint-plugin-unicorn", + "eslint-plugin-jsdoc", + "eslint-plugin-prefer-arrow", + "eslint-plugin-react", + "@typescript-eslint/eslint-plugin", + "eslint-config-prettier", + "eslint-plugin-prettier" +] diff --git a/packages/common-ts/package.json b/packages/common-ts/package.json index bed116d53..362de6fa1 100644 --- a/packages/common-ts/package.json +++ b/packages/common-ts/package.json @@ -17,7 +17,7 @@ "lint": "yarn lint:fix && yarn lint:check", "pre-commit": "lint-staged", "test": "ts-mocha test/*.spec.ts", - "test:coverage": "echo 'no coverage'" + "test:coverage": "nyc ts-mocha test/*.spec.ts && nyc merge .nyc_output coverage.json" }, "keywords": [ "optimism", @@ -48,8 +48,7 @@ "pino": "^6.11.3", "pino-multi-stream": "^5.3.0", "pino-sentry": "^0.7.0", - "prom-client": "^13.1.0", - "qs": "^6.10.5" + "prom-client": "^13.1.0" }, "devDependencies": { "@ethersproject/abstract-provider": "^5.7.0", diff --git a/packages/common-ts/test/metrics.spec.ts b/packages/common-ts/test/metrics.spec.ts index f0a46cdda..eb837d1da 100644 --- a/packages/common-ts/test/metrics.spec.ts +++ b/packages/common-ts/test/metrics.spec.ts @@ -3,11 +3,11 @@ import request from 'supertest' import chai = require('chai') const expect = chai.expect -import { Logger, Metrics, createMetricsServer } from '../src' +import { Logger, LegacyMetrics, createMetricsServer } from '../src' describe('Metrics', () => { it('shoud serve metrics', async () => { - const metrics = new Metrics({ + const metrics = new LegacyMetrics({ prefix: 'test_metrics', }) const registry = metrics.registry diff --git a/yarn.lock b/yarn.lock index 6b141261d..21435f664 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17871,13 +17871,6 @@ qs@^6.10.3, qs@^6.9.4: dependencies: side-channel "^1.0.4" -qs@^6.10.5: - version "6.10.5" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" - integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== - dependencies: - side-channel "^1.0.4" - qs@^6.4.0, qs@^6.7.0: version "6.10.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" From 0edc80eed5f9b2e94c41ea0fcb4c8eabf409d564 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Tue, 21 Mar 2023 14:25:04 -0500 Subject: [PATCH 128/404] feat(docs/op-stack): Corrected some mistakes --- .../src/docs/build/getting-started.md | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/docs/op-stack/src/docs/build/getting-started.md b/docs/op-stack/src/docs/build/getting-started.md index 37d86a8e3..f0960847f 100644 --- a/docs/op-stack/src/docs/build/getting-started.md +++ b/docs/op-stack/src/docs/build/getting-started.md @@ -390,9 +390,7 @@ Head over to the `op-node` package and start the `op-node` using the following c --rollup.config=./rollup.json \ --rpc.addr=0.0.0.0 \ --rpc.port=8547 \ - --p2p.listen.ip=0.0.0.0 \ - --p2p.listen.tcp=9003 \ - --p2p.listen.udp=9003 \ + --p2p.disable \ --rpc.enable-admin \ --p2p.sequencer.key= \ --l1= \ @@ -402,6 +400,26 @@ Head over to the `op-node` package and start the `op-node` using the following c Once you run this command, you should start seeing the `op-node` begin to process all of the L1 information after the starting block number that you picked earlier. Once the `op-node` has enough information, it’ll begin sending Engine API payloads to `op-geth`. At that point, you’ll start to see blocks being created inside of `op-geth`. We’re live! +::: tip Peer to peer synchronization + +If you use a chain ID that is also used by others, for example the default (42069), your `op-node` will try to use peer to peer to speed up synchronization. +These attempts will fail, because they will be signed with the wrong key, but they will waste time and network resources. + +To avoid this , we start with peer to peer synchronization disabled (`--p2p.disable`). +Once you have multiple nodes, it makes sense to use these command line parameters to synchronize between them without getting confused by other blockchains. + +``` + --p2p.static= + --p2p.listen.ip=0.0.0.0 \ + --p2p.listen.tcp=9003 \ + --p2p.listen.udp=9003 \ +``` + +::: + + + + ## Run op-batcher The final component necessary to put all the pieces together is the `op-batcher`. The `op-batcher` takes transactions from the Sequencer and publishes those transactions to L1. Once transactions are on L1, they’re officially part of the Rollup. Without the `op-batcher`, transactions sent to the Sequencer would never make it to L1 and wouldn’t become part of the canonical chain. The `op-batcher` is critical! @@ -531,6 +549,20 @@ To restart the blockchain, use the same order of components you did when you ini 1. `op-node` 1. `op-batcher` +::: tip Synchronization takes time + +`op-batcher` might have warning messages similar to: + +``` +WARN [03-21|14:13:55.248] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded" +WARN [03-21|14:13:57.328] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded" +``` + +This means that `op-node` is not yet synchronized up to the present time. +Just wait until it is. + +::: + ### Errors @@ -554,27 +586,18 @@ echo "" > datadir/block-signer-key ./build/bin/geth init --datadir=./datadir ./genesis.json ``` -#### `op-geth` not synchronized yet -You might get these errors when starting `op-node`: - -``` -t=2023-03-21T11:02:16-0500 lvl=info msg="Initializing Rollup Node" -ERROR[03-21|11:02:16.891] Unable to create the rollup node config error="failed to load p2p config: failed to load p2p discovery options: failed to open discovery db: resource temporarily unavailable" -CRIT [03-21|11:02:16.902] Application failed message="failed to load p2p config: failed to load p2p discovery options: failed to open discovery db: resource temporarily unavailable" -``` +#### Batcher out of ETH -This usually means that `op-geth` is still synchronizing. -You will see these messages on its console: +If `op-batcher` runs out of ETH, it cannot submit write new transaction batches to L1. +You will get error messages similar to this one: ``` -INFO [03-21|11:01:01.296] Chain head was updated number=23650 hash=64d626..3762b7 root=b8f5cf..78bfc0 elapsed="69.897µs" age=8h36m41s -INFO [03-21|11:01:01.373] Starting work on payload id=0x76c8267ac16b0ec0 -INFO [03-21|11:01:01.374] Updated payload id=0x76c8267ac16b0ec0 number=23651 hash=e846d2..af628e txs=1 gas=46913 fees=0 root=85ef2b..33ce37 elapsed="297.264µs" -INFO [03-21|11:01:01.376] Stopping work on payload id=0x76c8267ac16b0ec0 reason=delivery +INFO [03-21|14:22:32.754] publishing transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515 +ERROR[03-21|14:22:32.844] unable to publish transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515 err="insufficient funds for gas * price + value" ``` -In this case wait to start `op-node` until `op-geth` is fully synchronized. +Just send more ETH and to the batcher, and the problem will be resolved. ## Adding nodes From a5387c5b5bf142c4eaabf80da9d00229005baeb4 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 21 Mar 2023 15:29:43 -0400 Subject: [PATCH 129/404] fix missing cli flags in the op-batcher --- op-batcher/batcher/driver.go | 17 +++++++++-------- op-batcher/flags/flags.go | 2 ++ op-e2e/migration_test.go | 2 ++ op-e2e/setup.go | 2 ++ ops-bedrock/docker-compose.yml | 2 ++ 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index cfc9477cd..dd99a418f 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -84,14 +84,15 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri } batcherCfg := Config{ - L1Client: l1Client, - L2Client: l2Client, - RollupNode: rollupClient, - PollInterval: cfg.PollInterval, - TxManagerConfig: txManagerConfig, - TxManagerTimeout: cfg.TxManagerTimeout, - From: fromAddress, - Rollup: rcfg, + L1Client: l1Client, + L2Client: l2Client, + RollupNode: rollupClient, + PollInterval: cfg.PollInterval, + TxManagerConfig: txManagerConfig, + TxManagerTimeout: cfg.TxManagerTimeout, + OfflineGasEstimation: cfg.OfflineGasEstimation, + From: fromAddress, + Rollup: rcfg, Channel: ChannelConfig{ SeqWindowSize: rcfg.SeqWindowSize, ChannelTimeout: rcfg.ChannelTimeout, diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index bd173cb87..30b49fd2d 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -153,6 +153,8 @@ var requiredFlags = []cli.Flag{ } var optionalFlags = []cli.Flag{ + OfflineGasEstimationFlag, + TxManagerTimeoutFlag, MaxChannelDurationFlag, MaxL1TxSizeBytesFlag, TargetL1TxSizeBytesFlag, diff --git a/op-e2e/migration_test.go b/op-e2e/migration_test.go index 69bf616e6..8fceae120 100644 --- a/op-e2e/migration_test.go +++ b/op-e2e/migration_test.go @@ -327,6 +327,8 @@ func TestMigration(t *testing.T) { L1EthRpc: forkedL1URL, L2EthRpc: gethNode.WSEndpoint(), RollupRpc: rollupNode.HTTPEndpoint(), + TxManagerTimeout: 10 * time.Minute, + OfflineGasEstimation: true, MaxChannelDuration: 1, MaxL1TxSize: 120_000, TargetL1TxSize: 100_000, diff --git a/op-e2e/setup.go b/op-e2e/setup.go index fea710838..c1cb106a7 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -586,6 +586,8 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { L1EthRpc: sys.Nodes["l1"].WSEndpoint(), L2EthRpc: sys.Nodes["sequencer"].WSEndpoint(), RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(), + TxManagerTimeout: 10 * time.Minute, + OfflineGasEstimation: true, MaxChannelDuration: 1, MaxL1TxSize: 120_000, TargetL1TxSize: 100_000, diff --git a/ops-bedrock/docker-compose.yml b/ops-bedrock/docker-compose.yml index e53a159b0..7bc3c8fab 100644 --- a/ops-bedrock/docker-compose.yml +++ b/ops-bedrock/docker-compose.yml @@ -121,6 +121,8 @@ services: OP_BATCHER_L1_ETH_RPC: http://l1:8545 OP_BATCHER_L2_ETH_RPC: http://l2:8545 OP_BATCHER_ROLLUP_RPC: http://op-node:8545 + TX_MANAGER_TIMEOUT: 10m + OFFLINE_GAS_ESTIMATION: false OP_BATCHER_MAX_CHANNEL_DURATION: 1 OP_BATCHER_MAX_L1_TX_SIZE_BYTES: 120000 OP_BATCHER_TARGET_L1_TX_SIZE_BYTES: 100000 From fbe13302d0a52692a9e2c20a3392680d9abbce4a Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 21 Mar 2023 20:22:02 +0100 Subject: [PATCH 130/404] op-node: schedule by block-number, re-attempt RPC alt-sync requests with backoff --- op-node/sources/sync_client.go | 58 ++++++++++++++++------------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/op-node/sources/sync_client.go b/op-node/sources/sync_client.go index b6e41c05d..f7513a2ec 100644 --- a/op-node/sources/sync_client.go +++ b/op-node/sources/sync_client.go @@ -12,6 +12,8 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/sources/caching" + "github.com/ethereum-optimism/optimism/op-service/backoff" + "github.com/ethereum/go-ethereum/log" "github.com/libp2p/go-libp2p/core/peer" ) @@ -21,15 +23,10 @@ var ErrNoUnsafeL2PayloadChannel = errors.New("unsafeL2Payloads channel must not // RpcSyncPeer is a mock PeerID for the RPC sync client. var RpcSyncPeer peer.ID = "ALT_RPC_SYNC" -// Limit the maximum range to request at a time. -const maxRangePerWorker = 10 - +// receivePayload queues the received payload for processing. +// This may return an error if there's no capacity for the payload. type receivePayload = func(ctx context.Context, from peer.ID, payload *eth.ExecutionPayload) error -type syncRequest struct { - start, end uint64 -} - type RPCSync interface { io.Closer // Start starts an additional worker syncing job @@ -41,7 +38,7 @@ type RPCSync interface { type SyncClient struct { *L2Client - requests chan syncRequest + requests chan uint64 resCtx context.Context resCancel context.CancelFunc @@ -71,7 +68,7 @@ func NewSyncClient(receiver receivePayload, client client.RPC, log log.Logger, m L2Client: l2Client, resCtx: resCtx, resCancel: resCancel, - requests: make(chan syncRequest, 128), + requests: make(chan uint64, 128), receivePayload: receiver, }, nil } @@ -101,15 +98,9 @@ func (s *SyncClient) RequestL2Range(ctx context.Context, start, end uint64) erro s.log.Info("Scheduling to fetch missing payloads from backup RPC", "start", start, "end", end, "size", end-start) - for i := start; i < end; i += maxRangePerWorker { - r := syncRequest{start: i, end: i + maxRangePerWorker} - if r.end > end { - r.end = end - } - s.log.Info("Scheduling range request", "start", r.start, "end", r.end, "size", r.end-r.start) - // schedule new range to be requested + for i := start; i < end; i++ { select { - case s.requests <- r: + case s.requests <- i: case <-ctx.Done(): return ctx.Err() } @@ -122,26 +113,33 @@ func (s *SyncClient) eventLoop() { defer s.wg.Done() s.log.Info("Starting sync client event loop") + backoffStrategy := &backoff.ExponentialStrategy{ + Min: 1000, + Max: 20_000, + MaxJitter: 250, + } + for { select { case <-s.resCtx.Done(): s.log.Debug("Shutting down RPC sync worker") return - case r := <-s.requests: - // Limit the maximum time for fetching payloads - ctx, cancel := context.WithTimeout(s.resCtx, time.Second*10) - // We are only fetching one block at a time here. - err := s.fetchUnsafeBlockFromRpc(ctx, r.start) - cancel() - if err != nil { - s.log.Error("failed syncing L2 block via RPC", "err", err) - } else { - r.start += 1 // continue with next block + case reqNum := <-s.requests: + err := backoff.DoCtx(s.resCtx, 5, backoffStrategy, func() error { + // Limit the maximum time for fetching payloads + ctx, cancel := context.WithTimeout(s.resCtx, time.Second*10) + defer cancel() + // We are only fetching one block at a time here. + return s.fetchUnsafeBlockFromRpc(ctx, reqNum) + }) + if err == s.resCtx.Err() { + return } - // Reschedule - if r.start < r.end { + if err != nil { + s.log.Error("failed syncing L2 block via RPC", "err", err, "num", reqNum) + // Reschedule at end of queue select { - case s.requests <- r: + case s.requests <- reqNum: default: // drop syncing job if we are too busy with sync jobs already. } From ca597da5d9a5b2880b8345259d293a119228a202 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 21 Mar 2023 15:36:42 -0400 Subject: [PATCH 131/404] fix for semgrep --- op-e2e/setup.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/op-e2e/setup.go b/op-e2e/setup.go index c1cb106a7..dd2ad5f89 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -582,11 +582,12 @@ func (cfg SystemConfig) Start(_opts ...SystemConfigOption) (*System, error) { } // Batch Submitter + txManagerTimeout := 10 * time.Minute sys.BatchSubmitter, err = bss.NewBatchSubmitterFromCLIConfig(bss.CLIConfig{ L1EthRpc: sys.Nodes["l1"].WSEndpoint(), L2EthRpc: sys.Nodes["sequencer"].WSEndpoint(), RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(), - TxManagerTimeout: 10 * time.Minute, + TxManagerTimeout: txManagerTimeout, OfflineGasEstimation: true, MaxChannelDuration: 1, MaxL1TxSize: 120_000, From ddc14c487b383bf48dd61e6c79976056df8aef9f Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Mon, 20 Mar 2023 11:18:01 -0400 Subject: [PATCH 132/404] fix peer scoring and related metrics --- op-node/flags/p2p_flags.go | 12 +++ op-node/metrics/metrics.go | 40 +++++----- op-node/p2p/band_scorer_test.go | 57 ++++++++++++++ op-node/p2p/cli/load_config.go | 15 ++++ op-node/p2p/config.go | 7 ++ op-node/p2p/gossip.go | 4 +- op-node/p2p/mocks/BandScorer.go | 58 +++++++++++++++ op-node/p2p/mocks/ConnectionGater.go | 7 +- op-node/p2p/mocks/GossipMetricer.go | 14 ++-- op-node/p2p/mocks/PeerGater.go | 2 +- op-node/p2p/mocks/Peerstore.go | 2 +- op-node/p2p/peer_scorer.go | 106 ++++++++++++++++++++++++--- op-node/p2p/peer_scorer_test.go | 37 +++++++--- op-node/p2p/peer_scores.go | 2 +- op-node/p2p/peer_scores_test.go | 19 +++-- op-node/p2p/prepared.go | 4 + ops-bedrock/docker-compose.yml | 2 + 17 files changed, 327 insertions(+), 61 deletions(-) create mode 100644 op-node/p2p/band_scorer_test.go create mode 100644 op-node/p2p/mocks/BandScorer.go diff --git a/op-node/flags/p2p_flags.go b/op-node/flags/p2p_flags.go index 578fd9395..379647f22 100644 --- a/op-node/flags/p2p_flags.go +++ b/op-node/flags/p2p_flags.go @@ -34,6 +34,15 @@ var ( Value: "none", EnvVar: p2pEnv("PEER_SCORING"), } + PeerScoreBands = cli.StringFlag{ + Name: "p2p.score.bands", + Usage: "Sets the peer score bands used primarily for peer score metrics. " + + "Should be provided in following format: :