From 681c9878e37bb930b3f3bd4f62cbcc8fbe3d2819 Mon Sep 17 00:00:00 2001 From: Joan Esteban <129153821+joanestebanr@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:48:05 +0200 Subject: [PATCH] feat: seq sender sanity check l1infotree (#86) --- sequencesender/sequencesender.go | 19 +++++--- sequencesender/sequencesender_test.go | 44 ++++++++++++++++++ sequencesender/txbuilder/banana_base.go | 16 +++++++ sequencesender/txbuilder/banana_base_test.go | 49 +++++++++++++++++++- sequencesender/txbuilder/banana_types.go | 35 ++++++++++++++ 5 files changed, 154 insertions(+), 9 deletions(-) diff --git a/sequencesender/sequencesender.go b/sequencesender/sequencesender.go index 3431d3fe..1d76d3c0 100644 --- a/sequencesender/sequencesender.go +++ b/sequencesender/sequencesender.go @@ -214,6 +214,7 @@ func (s *SequenceSender) purgeSequences() { // Purge the information of batches that are already virtualized s.mutexSequence.Lock() + defer s.mutexSequence.Unlock() truncateUntil := 0 toPurge := make([]uint64, 0) for i := 0; i < len(s.sequenceList); i++ { @@ -240,7 +241,6 @@ func (s *SequenceSender) purgeSequences() { } s.logger.Infof("batches purged count: %d, fromBatch: %d, toBatch: %d", len(toPurge), firstPurged, lastPurged) } - s.mutexSequence.Unlock() } // purgeEthTx purges transactions from memory structures @@ -252,6 +252,7 @@ func (s *SequenceSender) purgeEthTx(ctx context.Context) { // Purge old transactions that are finalized s.mutexEthTx.Lock() + defer s.mutexEthTx.Unlock() timePurge := time.Now().Add(-s.cfg.WaitPeriodPurgeTxFile.Duration) toPurge := make([]common.Hash, 0) for hash, data := range s.ethTransactions { @@ -289,7 +290,6 @@ func (s *SequenceSender) purgeEthTx(ctx context.Context) { } s.logger.Infof("txs purged count: %d, fromNonce: %d, toNonce: %d", len(toPurge), firstPurged, lastPurged) } - s.mutexEthTx.Unlock() } // syncEthTxResults syncs results from L1 for transactions in the memory structure @@ -1168,7 +1168,9 @@ func (s *SequenceSender) addInfoSequenceBatchEnd(batch *datastream.BatchEnd) { // addNewBatchL2Block adds a new L2 block to the work in progress batch func (s *SequenceSender) addNewBatchL2Block(l2Block *datastream.L2Block) { s.mutexSequence.Lock() - s.logger.Infof(".....new L2 block, number %d (batch %d)", l2Block.Number, l2Block.BatchNumber) + defer s.mutexSequence.Unlock() + s.logger.Infof(".....new L2 block, number %d (batch %d) l1infotree %d", + l2Block.Number, l2Block.BatchNumber, l2Block.L1InfotreeIndex) // Current batch data := s.sequenceData[s.wipBatch] @@ -1183,7 +1185,12 @@ func (s *SequenceSender) addNewBatchL2Block(l2Block *datastream.L2Block) { ) } data.batch.SetLastCoinbase(common.BytesToAddress(l2Block.Coinbase)) - data.batch.SetL1InfoTreeIndex(l2Block.L1InfotreeIndex) + if l2Block.L1InfotreeIndex != 0 { + data.batch.SetL1InfoTreeIndex(l2Block.L1InfotreeIndex) + } else { + s.logger.Warnf("L2 Block L1InfotreeIndex is 0, we don't change batch L1InfotreeIndex (%d)", + data.batch.L1InfoTreeIndex()) + } // New L2 block raw newBlockRaw := state.L2BlockRaw{} @@ -1200,13 +1207,12 @@ func (s *SequenceSender) addNewBatchL2Block(l2Block *datastream.L2Block) { blockRaw.DeltaTimestamp = l2Block.DeltaTimestamp blockRaw.IndexL1InfoTree = l2Block.L1InfotreeIndex } - - s.mutexSequence.Unlock() } // addNewBlockTx adds a new Tx to the current L2 block func (s *SequenceSender) addNewBlockTx(l2Tx *datastream.Transaction) { s.mutexSequence.Lock() + defer s.mutexSequence.Unlock() s.logger.Debugf("........new tx, length %d EGP %d SR %x..", len(l2Tx.Encoded), l2Tx.EffectiveGasPricePercentage, l2Tx.ImStateRoot[:8], ) @@ -1229,7 +1235,6 @@ func (s *SequenceSender) addNewBlockTx(l2Tx *datastream.Transaction) { // Add Tx blockRaw.Transactions = append(blockRaw.Transactions, l2TxRaw) - s.mutexSequence.Unlock() } // getWipL2Block returns index of the array and pointer to the current L2 block (helper func) diff --git a/sequencesender/sequencesender_test.go b/sequencesender/sequencesender_test.go index c16fda42..f839cfca 100644 --- a/sequencesender/sequencesender_test.go +++ b/sequencesender/sequencesender_test.go @@ -4,7 +4,10 @@ import ( "testing" "github.com/0xPolygon/cdk/log" + "github.com/0xPolygon/cdk/sequencesender/txbuilder" "github.com/0xPolygon/cdk/state" + "github.com/0xPolygon/cdk/state/datastream" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -71,3 +74,44 @@ func TestStreamTx(t *testing.T) { printBatch(decodedBatch, true, true) } + +func TestAddNewBatchL2Block(t *testing.T) { + logger := log.GetDefaultLogger() + txBuilder := txbuilder.NewTxBuilderBananaZKEVM(logger, nil, nil, bind.TransactOpts{}, 100, nil, nil, nil) + sut := SequenceSender{ + logger: logger, + cfg: Config{}, + ethTransactions: make(map[common.Hash]*ethTxData), + ethTxData: make(map[common.Hash][]byte), + sequenceData: make(map[uint64]*sequenceData), + validStream: false, + latestStreamBatch: 0, + seqSendingStopped: false, + TxBuilder: txBuilder, + } + + l2Block := datastream.L2Block{ + Number: 1, + BatchNumber: 1, + L1InfotreeIndex: 1, + } + sut.addNewSequenceBatch(&l2Block) + l2Block = datastream.L2Block{ + Number: 2, + BatchNumber: 1, + L1InfotreeIndex: 0, + } + sut.addNewBatchL2Block(&l2Block) + data := sut.sequenceData[sut.wipBatch] + // L1InfotreeIndex 0 is ignored + require.Equal(t, uint32(1), data.batch.L1InfoTreeIndex(), "new block have index=0 and is ignored") + + l2Block = datastream.L2Block{ + Number: 2, + BatchNumber: 1, + L1InfotreeIndex: 5, + } + sut.addNewBatchL2Block(&l2Block) + data = sut.sequenceData[sut.wipBatch] + require.Equal(t, uint32(5), data.batch.L1InfoTreeIndex(), "new block have index=5 and is set") +} diff --git a/sequencesender/txbuilder/banana_base.go b/sequencesender/txbuilder/banana_base.go index 7b451ed8..6d191c4a 100644 --- a/sequencesender/txbuilder/banana_base.go +++ b/sequencesender/txbuilder/banana_base.go @@ -134,10 +134,26 @@ func (t *TxBuilderBananaBase) NewSequence( sequence.OldAccInputHash = oldAccInputHash sequence.AccInputHash = accInputHash + + err = SequenceSanityCheck(sequence) + if err != nil { + return nil, fmt.Errorf("sequenceSanityCheck fails. Err: %w", err) + } res := NewBananaSequence(*sequence) return res, nil } +func SequenceSanityCheck(seq *etherman.SequenceBanana) error { + maxL1InfoIndex, err := calculateMaxL1InfoTreeIndexInsideSequence(seq) + if err != nil { + return err + } + if seq.CounterL1InfoRoot < maxL1InfoIndex+1 { + return fmt.Errorf("wrong CounterL1InfoRoot(%d): BatchL2Data (max=%d) ", seq.CounterL1InfoRoot, maxL1InfoIndex) + } + return nil +} + func (t *TxBuilderBananaBase) getL1InfoRoot(counterL1InfoRoot uint32) (common.Hash, error) { return t.globalExitRootContract.L1InfoRootMap(&bind.CallOpts{Pending: false}, counterL1InfoRoot) } diff --git a/sequencesender/txbuilder/banana_base_test.go b/sequencesender/txbuilder/banana_base_test.go index af4b05c0..3b449084 100644 --- a/sequencesender/txbuilder/banana_base_test.go +++ b/sequencesender/txbuilder/banana_base_test.go @@ -2,14 +2,17 @@ package txbuilder_test import ( "context" + "fmt" "math/big" "testing" + "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/l1infotreesync" "github.com/0xPolygon/cdk/log" "github.com/0xPolygon/cdk/sequencesender/seqsendertypes" "github.com/0xPolygon/cdk/sequencesender/txbuilder" "github.com/0xPolygon/cdk/sequencesender/txbuilder/mocks_txbuilder" + "github.com/0xPolygon/cdk/state" "github.com/0xPolygon/cdk/state/datastream" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -31,8 +34,15 @@ func TestBananaBaseNewSequenceEmpty(t *testing.T) { seq, err := testData.sut.NewSequence(context.TODO(), nil, common.Address{}) require.NotNil(t, seq) require.NoError(t, err) - // TODO check values - // require.Equal(t, lastAcc, seq.LastAccInputHash()) +} + +func TestBananaBaseNewSequenceErrorHeaderByNumber(t *testing.T) { + testData := newBananaBaseTestData(t) + testData.l1Client.On("HeaderByNumber", mock.Anything, mock.Anything). + Return(nil, fmt.Errorf("error")) + seq, err := testData.sut.NewSequence(context.TODO(), nil, common.Address{}) + require.Nil(t, seq) + require.Error(t, err) } func TestBananaBaseNewBatchFromL2Block(t *testing.T) { @@ -79,6 +89,41 @@ func TestBananaBaseNewSequenceBatch(t *testing.T) { // TODO: check that the seq have the right values } +func TestBananaSanityCheck(t *testing.T) { + batch := state.BatchRawV2{ + Blocks: []state.L2BlockRaw{ + { + BlockNumber: 1, + ChangeL2BlockHeader: state.ChangeL2BlockHeader{ + DeltaTimestamp: 1, + IndexL1InfoTree: 1, + }, + }, + }, + } + data, err := state.EncodeBatchV2(&batch) + require.NoError(t, err) + require.NotNil(t, data) + seq := etherman.SequenceBanana{ + CounterL1InfoRoot: 2, + Batches: []etherman.Batch{ + { + L2Data: data, + }, + }, + } + err = txbuilder.SequenceSanityCheck(&seq) + require.NoError(t, err, "inside batchl2data max is 1 and counter is 2 (2>=1+1)") + seq.CounterL1InfoRoot = 1 + err = txbuilder.SequenceSanityCheck(&seq) + require.Error(t, err, "inside batchl2data max is 1 and counter is 1. The batchl2data is not included in counter") +} + +func TestBananaSanityCheckNilSeq(t *testing.T) { + err := txbuilder.SequenceSanityCheck(nil) + require.Error(t, err, "nil sequence") +} + type testDataBananaBase struct { rollupContract *mocks_txbuilder.RollupBananaBaseContractor getContract *mocks_txbuilder.GlobalExitRootBananaContractor diff --git a/sequencesender/txbuilder/banana_types.go b/sequencesender/txbuilder/banana_types.go index c09095b6..c69d2876 100644 --- a/sequencesender/txbuilder/banana_types.go +++ b/sequencesender/txbuilder/banana_types.go @@ -5,6 +5,7 @@ import ( "github.com/0xPolygon/cdk/etherman" "github.com/0xPolygon/cdk/sequencesender/seqsendertypes" + "github.com/0xPolygon/cdk/state" "github.com/ethereum/go-ethereum/common" ) @@ -147,3 +148,37 @@ func (b *BananaSequence) LastVirtualBatchNumber() uint64 { func (b *BananaSequence) SetLastVirtualBatchNumber(batchNumber uint64) { b.SequenceBanana.LastVirtualBatchNumber = batchNumber } + +func calculateMaxL1InfoTreeIndexInsideL2Data(l2data []byte) (uint32, error) { + batchRawV2, err := state.DecodeBatchV2(l2data) + if err != nil { + return 0, fmt.Errorf("calculateMaxL1InfoTreeIndexInsideL2Data: error decoding batchL2Data, err:%w", err) + } + if batchRawV2 == nil { + return 0, fmt.Errorf("calculateMaxL1InfoTreeIndexInsideL2Data: batchRawV2 is nil") + } + maxIndex := uint32(0) + for _, block := range batchRawV2.Blocks { + if block.IndexL1InfoTree > maxIndex { + maxIndex = block.IndexL1InfoTree + } + } + return maxIndex, nil +} + +func calculateMaxL1InfoTreeIndexInsideSequence(seq *etherman.SequenceBanana) (uint32, error) { + if seq == nil { + return 0, fmt.Errorf("calculateMaxL1InfoTreeIndexInsideSequence: seq is nil") + } + maxIndex := uint32(0) + for _, batch := range seq.Batches { + index, err := calculateMaxL1InfoTreeIndexInsideL2Data(batch.L2Data) + if err != nil { + return 0, fmt.Errorf("calculateMaxL1InfoTreeIndexInsideBatches: error getting batch L1InfoTree , err:%w", err) + } + if index > maxIndex { + maxIndex = index + } + } + return maxIndex, nil +}