Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

feat(driver): check maxBytesPerTxList for compressed txlist bytes #783

Merged
merged 10 commits into from
May 3, 2024
41 changes: 16 additions & 25 deletions driver/chain_syncer/blob/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
txListDecomporessor "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 *txListDecomporessor.TxListDecompressor // Transactions list decompressor
// Used by BlockInserter
lastInsertedBlockID *big.Int
reorgDetectedFlag bool
Expand Down Expand Up @@ -72,7 +72,7 @@ func NewSyncer(
state: state,
progressTracker: progressTracker,
anchorConstructor: constructor,
txListValidator: txListValidator.NewTxListValidator(
txListDecompressor: txListDecomporessor.NewTxListDecompressor(
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
uint64(configs.BlockMaxGasLimit),
rpc.BlockMaxTxListBytes,
client.L2.ChainID,
Expand Down Expand Up @@ -246,38 +246,29 @@ func (s *Syncer) onBlockProposed(
}

// Decode transactions list.
var txListDecoder txlistfetcher.TxListFetcher
var txListFecher txlistFetcher.TxListFetcher
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
if event.Meta.BlobUsed {
txListDecoder = txlistfetcher.NewBlobTxListFetcher(s.rpc.L1Beacon, s.blobDatasource)
txListFecher = txlistFetcher.NewBlobTxListFetcher(s.rpc.L1Beacon, s.blobDatasource)
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
} else {
txListDecoder = new(txlistfetcher.CalldataFetcher)
txListFecher = new(txlistFetcher.CalldataFetcher)
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
}
txListBytes, err := txListDecoder.Fetch(ctx, tx, &event.Meta)
txListBytes, err := txListFecher.Fetch(ctx, tx, &event.Meta)
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
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.TryDecomporess(event.BlockId, txListBytes, event.Meta.BlobUsed),
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
&rawdb.L1Origin{
BlockID: event.BlockId,
L2BlockHash: common.Hash{}, // Will be set by taiko-geth.
Expand Down
76 changes: 76 additions & 0 deletions driver/txlist_decompressor/txlist_decomporessor.go
Original file line number Diff line number Diff line change
@@ -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,
}
}

// TryDecomporess 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) TryDecomporess(
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
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
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package txlistvalidator
package txlistdecompressor

import (
"crypto/rand"
Expand All @@ -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 (
Expand All @@ -30,48 +31,50 @@ var (
}
)

func TestIsTxListValid(t *testing.T) {
v := NewTxListValidator(
func TestDecomporess(t *testing.T) {
d := NewTxListDecompressor(
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
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.TryDecomporess(tt.blockID, tt.txListBytes, false))
})
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down
56 changes: 0 additions & 56 deletions pkg/txlist_validator/tx_list_validator.go

This file was deleted.

Loading