Commit 4a1a364a authored by Diederik Loerakker's avatar Diederik Loerakker Committed by GitHub

fix(op-node): temporary batch-builder reorg fix - don't omit empty batches (#2705)

Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 982cb980
...@@ -82,7 +82,6 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error { ...@@ -82,7 +82,6 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error {
} }
// BlockToBatch converts a L2 block to batch-data. // BlockToBatch converts a L2 block to batch-data.
// Empty L2 blocks (i.e. only a L1 info deposit tx) return a nil batch with nil error.
// Invalid L2 blocks may return an error. // Invalid L2 blocks may return an error.
func BlockToBatch(config *rollup.Config, block *types.Block) (*derive.BatchData, error) { func BlockToBatch(config *rollup.Config, block *types.Block) (*derive.BatchData, error) {
txs := block.Transactions() txs := block.Transactions()
...@@ -92,9 +91,6 @@ func BlockToBatch(config *rollup.Config, block *types.Block) (*derive.BatchData, ...@@ -92,9 +91,6 @@ func BlockToBatch(config *rollup.Config, block *types.Block) (*derive.BatchData,
if typ := txs[0].Type(); typ != types.DepositTxType { if typ := txs[0].Type(); typ != types.DepositTxType {
return nil, fmt.Errorf("expected first tx to be a deposit of L1 info, but got type: %d", typ) return nil, fmt.Errorf("expected first tx to be a deposit of L1 info, but got type: %d", typ)
} }
if len(txs) == 1 { // the L1 info deposit tx, but empty otherwise, no batch data to submit
return nil, nil
}
// encode non-deposit transactions // encode non-deposit transactions
var opaqueTxs []hexutil.Bytes var opaqueTxs []hexutil.Bytes
......
...@@ -219,7 +219,7 @@ func (n *nodeAPI) GetBatchBundle(ctx context.Context, req *BatchBundleRequest) ( ...@@ -219,7 +219,7 @@ func (n *nodeAPI) GetBatchBundle(ctx context.Context, req *BatchBundleRequest) (
var pruneCount int var pruneCount int
for { for {
if !bundleBuilder.HasNonEmptyCandidate() { if !bundleBuilder.HasCandidate() {
return bundleBuilder.Response(nil), nil return bundleBuilder.Response(nil), nil
} }
...@@ -236,7 +236,7 @@ func (n *nodeAPI) GetBatchBundle(ctx context.Context, req *BatchBundleRequest) ( ...@@ -236,7 +236,7 @@ func (n *nodeAPI) GetBatchBundle(ctx context.Context, req *BatchBundleRequest) (
// occur since our initial greedy estimate has a very small, bounded // occur since our initial greedy estimate has a very small, bounded
// error tolerance, so simply remove the last block and try again. // error tolerance, so simply remove the last block and try again.
if bundleSize > uint64(req.MaxSize) { if bundleSize > uint64(req.MaxSize) {
bundleBuilder.PruneLastNonEmpty() bundleBuilder.PruneLast()
pruneCount++ pruneCount++
continue continue
} }
......
...@@ -12,9 +12,7 @@ type BundleCandidate struct { ...@@ -12,9 +12,7 @@ type BundleCandidate struct {
// ID is the block ID of an L2 block. // ID is the block ID of an L2 block.
ID eth.BlockID ID eth.BlockID
// Batch is batch data drived from the L2 Block. If Batch is nil, the block // Batch is batch data drived from the L2 Block.
// is considered to be empty. Empty blocks do not contribute to the size of
// a bundle.
Batch *derive.BatchData Batch *derive.BatchData
} }
...@@ -24,7 +22,6 @@ type BundleCandidate struct { ...@@ -24,7 +22,6 @@ type BundleCandidate struct {
type BundleBuilder struct { type BundleBuilder struct {
prevBlockID eth.BlockID prevBlockID eth.BlockID
candidates []BundleCandidate candidates []BundleCandidate
numNonEmpty int
} }
// NewBundleBuilder creates a new instance of a BundleBuilder, where prevBlockID // NewBundleBuilder creates a new instance of a BundleBuilder, where prevBlockID
...@@ -33,52 +30,37 @@ func NewBundleBuilder(prevBlockID eth.BlockID) *BundleBuilder { ...@@ -33,52 +30,37 @@ func NewBundleBuilder(prevBlockID eth.BlockID) *BundleBuilder {
return &BundleBuilder{ return &BundleBuilder{
prevBlockID: prevBlockID, prevBlockID: prevBlockID,
candidates: nil, candidates: nil,
numNonEmpty: 0,
} }
} }
// AddCandidate appends a candidate block to the BundleBuilder. // AddCandidate appends a candidate block to the BundleBuilder.
func (b *BundleBuilder) AddCandidate(candidate BundleCandidate) { func (b *BundleBuilder) AddCandidate(candidate BundleCandidate) {
b.candidates = append(b.candidates, candidate) b.candidates = append(b.candidates, candidate)
if candidate.Batch != nil {
b.numNonEmpty++
}
} }
// HasNonEmptyCandidate returns true if there are a non-zero number of // HasCandidate returns true if there are a non-zero number of
// non-empty bundle candidates. // non-empty bundle candidates.
func (b *BundleBuilder) HasNonEmptyCandidate() bool { func (b *BundleBuilder) HasCandidate() bool {
return b.numNonEmpty > 0 return len(b.candidates) > 0
} }
// PruneLastNonEmpty removes the latest non-empty candidate block and all empty // PruneLast removes the last candidate block.
// blocks follow it. This method is used to reduce the size of the encoded // This method is used to reduce the size of the encoded
// bundle in order to satisfy the desired size constraints. // bundle in order to satisfy the desired size constraints.
func (b *BundleBuilder) PruneLastNonEmpty() { func (b *BundleBuilder) PruneLast() {
if b.numNonEmpty == 0 { if len(b.candidates) == 0 {
return return
} }
b.candidates = b.candidates[:len(b.candidates)-1]
for i := len(b.candidates) - 1; i >= 0; i-- {
candidate := b.candidates[i]
if candidate.Batch != nil {
b.candidates = b.candidates[:i]
b.numNonEmpty--
return
}
}
} }
// Batches returns a slice of all non-nil batches contained within the candidate // Batches returns a slice of all non-nil batches contained within the candidate
// blocks. // blocks.
func (b *BundleBuilder) Batches() []*derive.BatchData { func (b *BundleBuilder) Batches() []*derive.BatchData {
var batches = make([]*derive.BatchData, 0, b.numNonEmpty) var batches = make([]*derive.BatchData, 0, len(b.candidates))
for _, candidate := range b.candidates { for _, candidate := range b.candidates {
if candidate.Batch != nil { batches = append(batches, candidate.Batch)
batches = append(batches, candidate.Batch)
}
} }
return batches return batches
} }
......
...@@ -38,7 +38,7 @@ func createResponse( ...@@ -38,7 +38,7 @@ func createResponse(
func TestNewBundleBuilder(t *testing.T) { func TestNewBundleBuilder(t *testing.T) {
builder := node.NewBundleBuilder(testPrevBlockID) builder := node.NewBundleBuilder(testPrevBlockID)
require.False(t, builder.HasNonEmptyCandidate()) require.False(t, builder.HasCandidate())
require.Equal(t, builder.Batches(), []*derive.BatchData{}) require.Equal(t, builder.Batches(), []*derive.BatchData{})
expResponse := createResponse(testPrevBlockID, testPrevBlockID, nil) expResponse := createResponse(testPrevBlockID, testPrevBlockID, nil)
require.Equal(t, expResponse, builder.Response(nil)) require.Equal(t, expResponse, builder.Response(nil))
...@@ -49,31 +49,14 @@ func TestNewBundleBuilder(t *testing.T) { ...@@ -49,31 +49,14 @@ func TestNewBundleBuilder(t *testing.T) {
func TestBundleBuilderAddCandidate(t *testing.T) { func TestBundleBuilderAddCandidate(t *testing.T) {
builder := node.NewBundleBuilder(testPrevBlockID) builder := node.NewBundleBuilder(testPrevBlockID)
// Add an empty candidate. // Add candidate.
blockID6 := eth.BlockID{
Number: 6,
Hash: common.HexToHash("0x66"),
}
builder.AddCandidate(node.BundleCandidate{
ID: blockID6,
Batch: nil,
})
// Should behave the same as completely empty builder except for updated
// last block ID fields.
require.False(t, builder.HasNonEmptyCandidate())
require.Equal(t, builder.Batches(), []*derive.BatchData{})
expResponse := createResponse(testPrevBlockID, blockID6, nil)
require.Equal(t, expResponse, builder.Response(nil))
// Add non-empty candidate.
blockID7 := eth.BlockID{ blockID7 := eth.BlockID{
Number: 7, Number: 7,
Hash: common.HexToHash("0x77"), Hash: common.HexToHash("0x77"),
} }
batchData7 := &derive.BatchData{ batchData7 := &derive.BatchData{
BatchV1: derive.BatchV1{ BatchV1: derive.BatchV1{
Epoch: 7, Epoch: 3,
Timestamp: 42, Timestamp: 42,
Transactions: []hexutil.Bytes{ Transactions: []hexutil.Bytes{
hexutil.Bytes([]byte{0x42, 0x07}), hexutil.Bytes([]byte{0x42, 0x07}),
...@@ -85,144 +68,35 @@ func TestBundleBuilderAddCandidate(t *testing.T) { ...@@ -85,144 +68,35 @@ func TestBundleBuilderAddCandidate(t *testing.T) {
Batch: batchData7, Batch: batchData7,
}) })
// HasNonEmptyCandidate should register that we have data to submit to L1, // HasCandidate should register that we have data to submit to L1,
// last block ID fields should also be updated. // last block ID fields should also be updated.
require.True(t, builder.HasNonEmptyCandidate()) require.True(t, builder.HasCandidate())
require.Equal(t, builder.Batches(), []*derive.BatchData{batchData7}) require.Equal(t, builder.Batches(), []*derive.BatchData{batchData7})
expResponse = createResponse(testPrevBlockID, blockID7, testBundleData) expResponse := createResponse(testPrevBlockID, blockID7, testBundleData)
require.Equal(t, expResponse, builder.Response(testBundleData)) require.Equal(t, expResponse, builder.Response(testBundleData))
// Add another empty block. // Add another block.
blockID8 := eth.BlockID{ blockID8 := eth.BlockID{
Number: 8, Number: 8,
Hash: common.HexToHash("0x88"), Hash: common.HexToHash("0x88"),
} }
batchData8 := &derive.BatchData{
BatchV1: derive.BatchV1{
Epoch: 5,
Timestamp: 44,
Transactions: []hexutil.Bytes{
hexutil.Bytes([]byte{0x13, 0x37}),
},
},
}
builder.AddCandidate(node.BundleCandidate{ builder.AddCandidate(node.BundleCandidate{
ID: blockID8, ID: blockID8,
Batch: nil, Batch: batchData8,
}) })
// Last block ID fields should be updated. // Last block ID fields should be updated.
require.True(t, builder.HasNonEmptyCandidate()) require.True(t, builder.HasCandidate())
require.Equal(t, builder.Batches(), []*derive.BatchData{batchData7}) require.Equal(t, builder.Batches(), []*derive.BatchData{batchData7, batchData8})
expResponse = createResponse(testPrevBlockID, blockID8, testBundleData) expResponse = createResponse(testPrevBlockID, blockID8, testBundleData)
require.Equal(t, expResponse, builder.Response(testBundleData)) require.Equal(t, expResponse, builder.Response(testBundleData))
} }
var pruneLastNonEmptyTests = []pruneLastNonEmptyTestCase{
{
name: "no candidates",
candidates: nil,
expResponse: createResponse(testPrevBlockID, testPrevBlockID, nil),
},
{
name: "only empty blocks",
candidates: []node.BundleCandidate{
{
ID: eth.BlockID{
Number: 6,
Hash: common.HexToHash("0x66"),
},
Batch: nil,
},
{
ID: eth.BlockID{
Number: 7,
Hash: common.HexToHash("0x77"),
},
Batch: nil,
},
},
expResponse: createResponse(
testPrevBlockID,
eth.BlockID{
Number: 7,
Hash: common.HexToHash("0x77"),
}, nil,
),
},
{
name: "last block is non empty",
candidates: []node.BundleCandidate{
{
ID: eth.BlockID{
Number: 6,
Hash: common.HexToHash("0x66"),
},
Batch: nil,
},
{
ID: eth.BlockID{
Number: 7,
Hash: common.HexToHash("0x77"),
},
Batch: &derive.BatchData{},
},
},
expResponse: createResponse(
testPrevBlockID,
eth.BlockID{
Number: 6,
Hash: common.HexToHash("0x66"),
}, nil,
),
},
{
name: "non empty block followed by empty block",
candidates: []node.BundleCandidate{
{
ID: eth.BlockID{
Number: 6,
Hash: common.HexToHash("0x66"),
},
Batch: nil,
},
{
ID: eth.BlockID{
Number: 7,
Hash: common.HexToHash("0x77"),
},
Batch: &derive.BatchData{},
},
{
ID: eth.BlockID{
Number: 8,
Hash: common.HexToHash("0x88"),
},
Batch: nil,
},
},
expResponse: createResponse(
testPrevBlockID,
eth.BlockID{
Number: 6,
Hash: common.HexToHash("0x66"),
}, nil,
),
},
}
// TestBundleBuilderPruneLastNonEmpty asserts that pruning the BundleBuilder
// always removes the last non-empty block, if one exists, and any subsequent
// empty blocks.
func TestBundleBuilderPruneLastNonEmpty(t *testing.T) {
for _, test := range pruneLastNonEmptyTests {
t.Run(test.name, test.run)
}
}
type pruneLastNonEmptyTestCase struct {
name string
candidates []node.BundleCandidate
expResponse *node.BatchBundleResponse
}
func (tc *pruneLastNonEmptyTestCase) run(t *testing.T) {
builder := node.NewBundleBuilder(testPrevBlockID)
for _, candidate := range tc.candidates {
builder.AddCandidate(candidate)
}
builder.PruneLastNonEmpty()
require.Equal(t, tc.expResponse, builder.Response(nil))
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment