diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d4a2b5d97..1b6181c1f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,20 +68,6 @@ jobs: version: 8.4.0 run_install: false - - name: Get pnpm store directory - id: pnpm-cache - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v3 - name: Setup pnpm cache - with: - path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - name: Install protocol dependencies working-directory: ${{ env.TAIKO_MONO_DIR }} run: cd ./packages/protocol && pnpm install diff --git a/driver/chain_syncer/blob/syncer.go b/driver/chain_syncer/blob/syncer.go index 3db1945b1..afc9f3832 100644 --- a/driver/chain_syncer/blob/syncer.go +++ b/driver/chain_syncer/blob/syncer.go @@ -26,20 +26,20 @@ import ( "github.com/taikoxyz/taiko-client/pkg/rpc" anchorTxConstructor "github.com/taikoxyz/taiko-client/driver/anchor_tx_constructor" - txlistfetcher "github.com/taikoxyz/taiko-client/driver/txlist_fetcher" + txListDecompressor "github.com/taikoxyz/taiko-client/driver/txlist_decompressor" + txlistFetcher "github.com/taikoxyz/taiko-client/driver/txlist_fetcher" eventIterator "github.com/taikoxyz/taiko-client/pkg/chain_iterator/event_iterator" - txListValidator "github.com/taikoxyz/taiko-client/pkg/txlist_validator" ) // Syncer responsible for letting the L2 execution engine catching up with protocol's latest // pending block through deriving L1 calldata. type Syncer struct { - ctx context.Context - rpc *rpc.Client - state *state.State - progressTracker *beaconsync.SyncProgressTracker // Sync progress tracker - anchorConstructor *anchorTxConstructor.AnchorTxConstructor // TaikoL2.anchor transactions constructor - txListValidator *txListValidator.TxListValidator // Transactions list validator + ctx context.Context + rpc *rpc.Client + state *state.State + progressTracker *beaconsync.SyncProgressTracker // Sync progress tracker + anchorConstructor *anchorTxConstructor.AnchorTxConstructor // TaikoL2.anchor transactions constructor + txListDecompressor *txListDecompressor.TxListDecompressor // Transactions list decompressor // Used by BlockInserter lastInsertedBlockID *big.Int reorgDetectedFlag bool @@ -72,7 +72,7 @@ func NewSyncer( state: state, progressTracker: progressTracker, anchorConstructor: constructor, - txListValidator: txListValidator.NewTxListValidator( + txListDecompressor: txListDecompressor.NewTxListDecompressor( uint64(configs.BlockMaxGasLimit), rpc.BlockMaxTxListBytes, client.L2.ChainID, @@ -246,38 +246,29 @@ func (s *Syncer) onBlockProposed( } // Decode transactions list. - var txListDecoder txlistfetcher.TxListFetcher + var txListFetcher txlistFetcher.TxListFetcher if event.Meta.BlobUsed { - txListDecoder = txlistfetcher.NewBlobTxListFetcher(s.rpc.L1Beacon, s.blobDatasource) + txListFetcher = txlistFetcher.NewBlobTxListFetcher(s.rpc.L1Beacon, s.blobDatasource) } else { - txListDecoder = new(txlistfetcher.CalldataFetcher) + txListFetcher = new(txlistFetcher.CalldataFetcher) } - txListBytes, err := txListDecoder.Fetch(ctx, tx, &event.Meta) + txListBytes, err := txListFetcher.Fetch(ctx, tx, &event.Meta) if err != nil { if errors.Is(err, rpc.ErrBlobInvalid) { log.Info("Invalid blob detected", "blockID", event.BlockId) txListBytes = []byte{} } else { - return fmt.Errorf("failed to decode tx list: %w", err) + return fmt.Errorf("failed to fetch tx list: %w", err) } } - if txListBytes, err = utils.Decompress(txListBytes); err != nil { - return fmt.Errorf("failed to decompress tx list bytes: %w", err) - } - - // If the transactions list is invalid, we simply insert an empty L2 block. - if !s.txListValidator.ValidateTxList(event.BlockId, txListBytes, event.Meta.BlobUsed) { - log.Info("Invalid transactions list, insert an empty L2 block instead", "blockID", event.BlockId) - txListBytes = []byte{} - } - + // Decompress the transactions list and try to insert a new head block to L2 EE. payloadData, err := s.insertNewHead( ctx, event, parent, s.state.GetHeadBlockID(), - txListBytes, + s.txListDecompressor.TryDecompress(event.BlockId, txListBytes, event.Meta.BlobUsed), &rawdb.L1Origin{ BlockID: event.BlockId, L2BlockHash: common.Hash{}, // Will be set by taiko-geth. diff --git a/driver/txlist_decompressor/txlist_decompressor.go b/driver/txlist_decompressor/txlist_decompressor.go new file mode 100644 index 000000000..9018af0ac --- /dev/null +++ b/driver/txlist_decompressor/txlist_decompressor.go @@ -0,0 +1,76 @@ +package txlistdecompressor + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/taikoxyz/taiko-client/internal/utils" +) + +// TxListDecompressor is responsible for validating and decompressing +// the transactions list in a TaikoL1.proposeBlock transaction. +type TxListDecompressor struct { + blockMaxGasLimit uint64 + maxBytesPerTxList uint64 + chainID *big.Int +} + +// NewTxListDecompressor creates a new TxListDecompressor instance based on giving configurations. +func NewTxListDecompressor( + blockMaxGasLimit uint64, + maxBytesPerTxList uint64, + chainID *big.Int, +) *TxListDecompressor { + return &TxListDecompressor{ + blockMaxGasLimit: blockMaxGasLimit, + maxBytesPerTxList: maxBytesPerTxList, + chainID: chainID, + } +} + +// TryDecompress validates and decompresses whether the transactions list in the TaikoL1.proposeBlock transaction's +// input data is valid, the rules are: +// - If the transaction list is empty, it's valid. +// - If the transaction list is not empty: +// 1. If the transaction list is using calldata, the compressed bytes of the transaction list must be +// less than or equal to maxBytesPerTxList. +// 2. The transaction list bytes must be able to be RLP decoded into a list of transactions. +func (v *TxListDecompressor) TryDecompress( + blockID *big.Int, + txListBytes []byte, + blobUsed bool, +) []byte { + // If the transaction list is empty, it's valid. + if len(txListBytes) == 0 { + return []byte{} + } + + // If calldata is used, the compressed bytes of the transaction list must be + // less than or equal to maxBytesPerTxList. + if !blobUsed && (len(txListBytes) > int(v.maxBytesPerTxList)) { + log.Info("Compressed transactions list binary too large", "length", len(txListBytes), "blockID", blockID) + return []byte{} + } + + var ( + txs types.Transactions + err error + ) + + // Decompress the transaction list bytes. + if txListBytes, err = utils.Decompress(txListBytes); err != nil { + log.Info("Failed to decompress tx list bytes", "blockID", blockID, "error", err) + return []byte{} + } + + // Try to RLP decode the transaction list bytes. + if err = rlp.DecodeBytes(txListBytes, &txs); err != nil { + log.Info("Failed to decode transactions list bytes", "blockID", blockID, "error", err) + return []byte{} + } + + log.Info("Transaction list is valid", "blockID", blockID) + return txListBytes +} diff --git a/pkg/txlist_validator/tx_list_validator_test.go b/driver/txlist_decompressor/txlist_decompressor_test.go similarity index 82% rename from pkg/txlist_validator/tx_list_validator_test.go rename to driver/txlist_decompressor/txlist_decompressor_test.go index f525bef46..a816b9e5f 100644 --- a/pkg/txlist_validator/tx_list_validator_test.go +++ b/driver/txlist_decompressor/txlist_decompressor_test.go @@ -1,4 +1,4 @@ -package txlistvalidator +package txlistdecompressor import ( "crypto/rand" @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" + "github.com/taikoxyz/taiko-client/internal/utils" ) var ( @@ -30,48 +31,50 @@ var ( } ) -func TestIsTxListValid(t *testing.T) { - v := NewTxListValidator( +func TestDecomporess(t *testing.T) { + d := NewTxListDecompressor( maxBlocksGasLimit, maxTxlistBytes, chainID, ) + compressed, err := utils.Compress(rlpEncodedTransactionBytes(1, true)) + require.NoError(t, err) + tests := []struct { - name string - blockID *big.Int - txListBytes []byte - isValid bool + name string + blockID *big.Int + txListBytes []byte + decompressed []byte }{ { "txListBytes binary too large", chainID, randBytes(maxTxlistBytes + 1), - false, + []byte{}, }, { "txListBytes not decodable to rlp", chainID, randBytes(0x1), - false, + []byte{}, }, { "success empty tx list", chainID, rlpEncodedTransactionBytes(0, true), - true, + []byte{}, }, { "success non-empty tx list", chainID, + compressed, rlpEncodedTransactionBytes(1, true), - true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - isValid := v.ValidateTxList(tt.blockID, tt.txListBytes, false) - require.Equal(t, tt.isValid, isValid) + require.Equal(t, tt.decompressed, d.TryDecompress(tt.blockID, tt.txListBytes, false)) }) } } diff --git a/pkg/txlist_validator/tx_list_validator.go b/pkg/txlist_validator/tx_list_validator.go deleted file mode 100644 index 1d1801a77..000000000 --- a/pkg/txlist_validator/tx_list_validator.go +++ /dev/null @@ -1,56 +0,0 @@ -package txlistvalidator - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" -) - -// TxListValidator is responsible for validating the transactions list in a TaikoL1.proposeBlock transaction. -type TxListValidator struct { - blockMaxGasLimit uint64 - maxBytesPerTxList uint64 - chainID *big.Int -} - -// NewTxListValidator creates a new TxListValidator instance based on giving configurations. -func NewTxListValidator( - blockMaxGasLimit uint64, - maxBytesPerTxList uint64, - chainID *big.Int, -) *TxListValidator { - return &TxListValidator{ - blockMaxGasLimit: blockMaxGasLimit, - maxBytesPerTxList: maxBytesPerTxList, - chainID: chainID, - } -} - -// ValidateTxList checks whether the transactions list in the TaikoL1.proposeBlock transaction's -// input data is valid. -func (v *TxListValidator) ValidateTxList( - blockID *big.Int, - txListBytes []byte, - blobUsed bool, -) (isValid bool) { - // If the transaction list is empty, it's valid. - if len(txListBytes) == 0 { - return true - } - - if !blobUsed && (len(txListBytes) > int(v.maxBytesPerTxList)) { - log.Info("Transactions list binary too large", "length", len(txListBytes), "blockID", blockID) - return false - } - - var txs types.Transactions - if err := rlp.DecodeBytes(txListBytes, &txs); err != nil { - log.Info("Failed to decode transactions list bytes", "blockID", blockID, "error", err) - return false - } - - log.Info("Transaction list is valid", "blockID", blockID) - return true -}