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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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"
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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
76 changes: 76 additions & 0 deletions driver/txlist_decompressor/txlist_decompressor.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,
}
}

// 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
}
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(
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))
})
}
}
Expand Down
56 changes: 0 additions & 56 deletions pkg/txlist_validator/tx_list_validator.go

This file was deleted.

Loading