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

Commit

Permalink
feat(driver): allow empty L2 blocks (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidtaikocha authored Dec 10, 2022
1 parent 26c38ea commit e9dc72f
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 53 deletions.
23 changes: 8 additions & 15 deletions driver/block_inserter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/taikoxyz/taiko-client/bindings"
"github.com/taikoxyz/taiko-client/bindings/encoding"
"github.com/taikoxyz/taiko-client/metrics"
eventIterator "github.com/taikoxyz/taiko-client/pkg/chain_iterator/event_iterator"
txListValidator "github.com/taikoxyz/taiko-client/pkg/tx_list_validator"
Expand Down Expand Up @@ -71,20 +70,12 @@ func (s *L2ChainSyncer) onBlockProposed(
return fmt.Errorf("failed to fetch original TaikoL1.proposeBlock transaction: %w", err)
}

txListBytes, err := encoding.UnpackTxListBytes(tx.Data())
// Check whether the transactions list is valid.
txListBytes, hint, invalidTxIndex, err := s.txListValidator.ValidateTxList(event.Id, tx.Data())
if err != nil {
log.Info(
"Skip the throw away block",
"blockID", event.Id,
"hint", "BINARY_NOT_DECODABLE",
"error", err,
)
return nil
return fmt.Errorf("failed to validate transactions list: %w", err)
}

// Check whether the transactions list is valid.
hint, invalidTxIndex := s.txListValidator.IsTxListValid(event.Id, txListBytes)

log.Info(
"Validate transactions list",
"blockID", event.Id,
Expand Down Expand Up @@ -186,9 +177,11 @@ func (s *L2ChainSyncer) insertNewHead(

// Insert a TaikoL2.anchor transaction at transactions list head
var txList []*types.Transaction
if err := rlp.DecodeBytes(txListBytes, &txList); err != nil {
log.Info("Ignore invalid txList bytes", "blockID", event.Id)
return nil, nil, err
if len(txListBytes) != 0 {
if err := rlp.DecodeBytes(txListBytes, &txList); err != nil {
log.Info("Ignore invalid txList bytes", "blockID", event.Id)
return nil, nil, err
}
}

// Assemble a TaikoL2.anchor transaction
Expand Down
27 changes: 27 additions & 0 deletions driver/block_inserter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package driver

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/taikoxyz/taiko-client/bindings/encoding"
"github.com/taikoxyz/taiko-client/testutils"
)

Expand Down Expand Up @@ -31,6 +33,31 @@ func (s *DriverTestSuite) TestProcessL1Blocks() {
s.Nil(err)

s.Greater(l2Head3.Number.Uint64(), l2Head2.Number.Uint64())

// Empty blocks
testutils.ProposeAndInsertEmptyBlocks(&s.ClientTestSuite, s.p, s.d.ChainSyncer())
s.Nil(err)

l2Head4, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
s.Nil(err)

s.Equal(l2Head3.Number.Uint64()+2, l2Head4.Number.Uint64())

for _, height := range []uint64{l2Head4.Number.Uint64(), l2Head4.Number.Uint64() - 1} {
header, err := s.d.rpc.L2.HeaderByNumber(context.Background(), new(big.Int).SetUint64(height))
s.Nil(err)

txCount, err := s.d.rpc.L2.TransactionCount(context.Background(), header.Hash())
s.Nil(err)
s.Equal(uint(1), txCount)

anchorTx, err := s.d.rpc.L2.TransactionInBlock(context.Background(), header.Hash(), 0)
s.Nil(err)

method, err := encoding.TaikoL2ABI.MethodById(anchorTx.Data())
s.Nil(err)
s.Equal("anchor", method.Name)
}
}

func (s *DriverTestSuite) TestGetInvalidateBlockTxOpts() {
Expand Down
20 changes: 11 additions & 9 deletions pkg/tx_list_validator/tx_list_validator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tx_list_validator

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -66,21 +65,24 @@ func NewTxListValidator(
func (v *TxListValidator) ValidateTxList(
blockID *big.Int,
proposeBlockTxInput []byte,
) (hint InvalidTxListReason, txIdx int, err error) {
txListBytes, err := encoding.UnpackTxListBytes(proposeBlockTxInput)
if err != nil {
return HintBinaryNotDecodable, 0, fmt.Errorf("failed to unpack raw transactions list bytes: %w", err)
) (txListBytes []byte, hint InvalidTxListReason, txIdx int, err error) {
if txListBytes, err = encoding.UnpackTxListBytes(proposeBlockTxInput); err != nil {
return nil, HintBinaryNotDecodable, 0, err
}

hint, txIdx = v.IsTxListValid(blockID, txListBytes)
if len(txListBytes) == 0 {
return txListBytes, HintOK, 0, nil
}

hint, txIdx = v.isTxListValid(blockID, txListBytes)

return hint, txIdx, nil
return txListBytes, hint, txIdx, nil
}

// IsTxListValid checks whether the transaction list is valid, must match
// isTxListValid checks whether the transaction list is valid, must match
// the validation rule defined in LibInvalidTxList.sol.
// ref: https://github.com/taikoxyz/taiko-mono/blob/main/packages/bindings/contracts/libs/LibInvalidTxList.sol
func (v *TxListValidator) IsTxListValid(blockID *big.Int, txListBytes []byte) (hint InvalidTxListReason, txIdx int) {
func (v *TxListValidator) isTxListValid(blockID *big.Int, txListBytes []byte) (hint InvalidTxListReason, txIdx int) {
if len(txListBytes) > int(v.txListMaxBytes) {
log.Info("Transactions list binary too large", "length", len(txListBytes), "blockID", blockID)
return HintBinaryTooLarge, 0
Expand Down
32 changes: 6 additions & 26 deletions pkg/tx_list_validator/tx_list_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math/rand"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -38,32 +39,11 @@ func TestValidateTxList(t *testing.T) {
minTxGasLimit,
chainID,
)
tests := []struct {
name string
blockID *big.Int
proposeBlockTxInput []byte
wantReason InvalidTxListReason
wantTxIdx int
wantErr bool
}{
{
"binary not decodable",
chainID,
randBytes(5),
HintBinaryNotDecodable,
0,
true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reason, txIdx, err := v.ValidateTxList(tt.blockID, tt.proposeBlockTxInput)
require.Equal(t, tt.wantReason, reason)
require.Equal(t, tt.wantTxIdx, txIdx)
require.Equal(t, tt.wantErr, err != nil)
})
}
// Binary is not unpackable
txListBytes, _, _, err := v.ValidateTxList(common.Big0, randBytes(5))
require.Empty(t, txListBytes)
require.NotNil(t, err)
}

func TestIsTxListValid(t *testing.T) {
Expand Down Expand Up @@ -134,7 +114,7 @@ func TestIsTxListValid(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reason, txIdx := v.IsTxListValid(tt.blockID, tt.txListBytes)
reason, txIdx := v.isTxListValid(tt.blockID, tt.txListBytes)
require.Equal(t, tt.wantReason, reason)
require.Equal(t, tt.wantTxIdx, txIdx)
})
Expand Down
4 changes: 1 addition & 3 deletions prover/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import (
)

var (
// Gas limit of TaikoL1.proveBlock and TaikoL1.proveBlockInvalid transactions.
// TODO: tune this value based when the on-chain solidity verifier is available.
maxPendingProofs = 10
)

Expand Down Expand Up @@ -247,7 +245,7 @@ func (p *Prover) onBlockProposed(
return err
}

hint, invalidTxIndex, err := p.txListValidator.ValidateTxList(event.Id, proposeBlockTx.Data())
_, hint, invalidTxIndex, err := p.txListValidator.ValidateTxList(event.Id, proposeBlockTx.Data())
if err != nil {
return err
}
Expand Down
6 changes: 6 additions & 0 deletions prover/prover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ func (s *ProverTestSuite) TestOnBlockProposed() {
s.Nil(s.p.onBlockProposed(context.Background(), e, func() {}))
s.Nil(s.p.submitValidBlockProof(context.Background(), <-s.p.proveValidProofCh))

// Empty blocks
for _, e = range testutils.ProposeAndInsertEmptyBlocks(&s.ClientTestSuite, s.proposer, s.d.ChainSyncer()) {
s.Nil(s.p.onBlockProposed(context.Background(), e, func() {}))
s.Nil(s.p.submitValidBlockProof(context.Background(), <-s.p.proveValidProofCh))
}

// Invalid block
e = testutils.ProposeAndInsertThrowawayBlock(&s.ClientTestSuite, s.proposer, s.d.ChainSyncer())
s.Nil(s.p.onBlockProposed(context.Background(), e, func() {}))
Expand Down
60 changes: 60 additions & 0 deletions testutils/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/taikoxyz/taiko-client/bindings"
)

Expand All @@ -28,6 +29,65 @@ func ProposeInvalidTxListBytes(s *ClientTestSuite, proposer Proposer) {
s.Nil(proposer.ProposeTxList(context.Background(), meta, commitTx, invalidTxListBytes, 1))
}

func ProposeAndInsertEmptyBlocks(
s *ClientTestSuite,
proposer Proposer,
chainSyncer L2ChainSyncer,
) []*bindings.TaikoL1ClientBlockProposed {
var events []*bindings.TaikoL1ClientBlockProposed

l1Head, err := s.RpcClient.L1.HeaderByNumber(context.Background(), nil)
s.Nil(err)

sink := make(chan *bindings.TaikoL1ClientBlockProposed)

sub, err := s.RpcClient.TaikoL1.WatchBlockProposed(nil, sink, nil)
s.Nil(err)
defer func() {
sub.Unsubscribe()
close(sink)
}()

// Zero byte txList
meta, commitTx, err := proposer.CommitTxList(context.Background(), []byte{}, 1024, 0)
s.Nil(err)

s.Nil(proposer.ProposeTxList(context.Background(), meta, commitTx, []byte{}, 0))

// RLP encoded empty list
var emptyTxs []types.Transaction
encoded, err := rlp.EncodeToBytes(emptyTxs)
s.Nil(err)

meta, commitTx, err = proposer.CommitTxList(context.Background(), encoded, 1024, 0)
s.Nil(err)

s.Nil(proposer.ProposeTxList(context.Background(), meta, commitTx, encoded, 0))

ProposeInvalidTxListBytes(s, proposer)

events = append(events, []*bindings.TaikoL1ClientBlockProposed{<-sink, <-sink}...)

_, isPending, err := s.RpcClient.L1.TransactionByHash(context.Background(), events[len(events)-1].Raw.TxHash)
s.Nil(err)
s.False(isPending)

newL1Head, err := s.RpcClient.L1.HeaderByNumber(context.Background(), nil)
s.Nil(err)
s.Greater(newL1Head.Number.Uint64(), l1Head.Number.Uint64())

syncProgress, err := s.RpcClient.L2.SyncProgress(context.Background())
s.Nil(err)
s.Nil(syncProgress)

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

s.Nil(chainSyncer.ProcessL1Blocks(ctx, newL1Head))

return events
}

// ProposeAndInsertThrowawayBlock proposes an invalid tx list and then insert it
// into L2 node's local chain.
func ProposeAndInsertThrowawayBlock(
Expand Down

0 comments on commit e9dc72f

Please sign in to comment.