Skip to content
This repository was archived by the owner on May 11, 2024. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit fe3177e

Browse files
authoredJan 12, 2023
feat(proposer): introduce types.TransactionsByPriceAndNonce to store pool content (#120)
1 parent 1beae15 commit fe3177e

File tree

6 files changed

+88
-78
lines changed

6 files changed

+88
-78
lines changed
 

‎pkg/rpc/methods.go

+16-23
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"math/big"
7-
"sort"
87
"time"
98

109
ethereum "github.com/ethereum/go-ethereum"
@@ -162,35 +161,29 @@ func (c *Client) WaitL1Origin(ctx context.Context, blockID *big.Int) (*rawdb.L1O
162161
// PoolContent represents a response body of a `txpool_content` RPC call.
163162
type PoolContent map[common.Address]map[string]*types.Transaction
164163

165-
type TxLists []types.Transactions
166-
167-
// ToTxLists flattens all transactions in pool content into transactions lists,
168-
// each list contains transactions from a single account sorted by nonce.
169-
func (pc PoolContent) ToTxLists() TxLists {
170-
txLists := make([]types.Transactions, 0)
171-
164+
// Len returns the number of transactions in the PoolContent.
165+
func (pc PoolContent) Len() int {
166+
len := 0
172167
for _, pendingTxs := range pc {
173-
var txsByNonce types.TxByNonce
174-
175-
for _, pendingTx := range pendingTxs {
176-
txsByNonce = append(txsByNonce, pendingTx)
168+
for range pendingTxs {
169+
len += 1
177170
}
178-
179-
sort.Sort(txsByNonce)
180-
181-
txLists = append(txLists, types.Transactions(txsByNonce))
182171
}
183172

184-
return txLists
173+
return len
185174
}
186175

187-
// Len returns the number of transactions inside the transactions lists.
188-
func (t TxLists) Len() int {
189-
var length = 0
190-
for _, pendingTxs := range t {
191-
length += len(pendingTxs)
176+
// ToTxsByPriceAndNonce creates a transaction set that can retrieve price sorted transactions in a nonce-honouring way.
177+
func (pc PoolContent) ToTxsByPriceAndNonce(chainID *big.Int) *types.TransactionsByPriceAndNonce {
178+
txs := map[common.Address]types.Transactions{}
179+
180+
for address, txsWithNonce := range pc {
181+
for _, tx := range txsWithNonce {
182+
txs[address] = append(txs[address], tx)
183+
}
192184
}
193-
return length
185+
186+
return types.NewTransactionsByPriceAndNonce(types.LatestSignerForChainID(chainID), txs, nil)
194187
}
195188

196189
// L2PoolContent fetches the transaction pool content from a L2 execution engine.

‎pkg/rpc/methods_test.go

+16-19
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/ethereum/go-ethereum/common"
99
"github.com/ethereum/go-ethereum/core/types"
10-
"github.com/ethereum/go-ethereum/log"
1110
"github.com/stretchr/testify/require"
1211
)
1312

@@ -32,7 +31,7 @@ func TestL2AccountNonce(t *testing.T) {
3231
require.Zero(t, nonce)
3332
}
3433

35-
func TestPoolContentToTxLists(t *testing.T) {
34+
func TestPoolContentLen(t *testing.T) {
3635
poolContent := &PoolContent{
3736
testAddress1: map[string]*types.Transaction{
3837
"6": types.NewTransaction(6, common.Address{}, common.Big0, 0, common.Big0, []byte{}),
@@ -45,25 +44,23 @@ func TestPoolContentToTxLists(t *testing.T) {
4544
},
4645
}
4746

48-
txLists := poolContent.ToTxLists()
49-
50-
require.Equal(t, 2, len(txLists))
51-
52-
for _, txs := range txLists {
53-
switch len(txs) {
54-
case 2:
55-
require.Equal(t, uint64(1), txs[0].Nonce())
56-
require.Equal(t, uint64(2), txs[1].Nonce())
57-
case 3:
58-
require.Equal(t, uint64(5), txs[0].Nonce())
59-
require.Equal(t, uint64(6), txs[1].Nonce())
60-
require.Equal(t, uint64(7), txs[2].Nonce())
61-
default:
62-
log.Crit("Invalid txs length")
63-
}
47+
require.Equal(t, 5, poolContent.Len())
48+
}
49+
50+
func TestToTxsByPriceAndNonce(t *testing.T) {
51+
poolContent := &PoolContent{
52+
testAddress1: map[string]*types.Transaction{
53+
"6": types.NewTransaction(6, common.Address{}, common.Big0, 0, common.Big0, []byte{}),
54+
"5": types.NewTransaction(5, common.Address{}, common.Big0, 0, common.Big0, []byte{}),
55+
"7": types.NewTransaction(7, common.Address{}, common.Big0, 0, common.Big0, []byte{}),
56+
},
57+
testAddress2: map[string]*types.Transaction{
58+
"2": types.NewTransaction(2, common.Address{}, common.Big0, 0, common.Big0, []byte{}),
59+
"1": types.NewTransaction(1, common.Address{}, common.Big0, 0, common.Big0, []byte{}),
60+
},
6461
}
6562

66-
require.Equal(t, 5, txLists.Len())
63+
require.NotNil(t, poolContent.ToTxsByPriceAndNonce(newTestClient(t).L2ChainID))
6764
}
6865

6966
func TestGetGenesisL1Header(t *testing.T) {

‎proposer/pool_content_splitter.go

+31-27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package proposer
22

33
import (
44
"fmt"
5+
"math/big"
56

67
"github.com/ethereum/go-ethereum/core/types"
78
"github.com/ethereum/go-ethereum/les/utils"
@@ -15,6 +16,7 @@ import (
1516
// which fetched from a `txpool_content` RPC call response into several smaller transactions lists
1617
// and make sure each splitted list satisfies the limits defined in Taiko protocol.
1718
type poolContentSplitter struct {
19+
chainID *big.Int
1820
shufflePoolContent bool
1921
maxTransactionsPerBlock uint64
2022
blockMaxGasLimit uint64
@@ -24,39 +26,41 @@ type poolContentSplitter struct {
2426

2527
// split splits the given transaction pool content to make each splitted
2628
// transactions list satisfies the rules defined in Taiko protocol.
27-
func (p *poolContentSplitter) split(poolContent rpc.PoolContent) [][]*types.Transaction {
29+
func (p *poolContentSplitter) split(poolContent rpc.PoolContent) []types.Transactions {
2830
var (
29-
txLists = poolContent.ToTxLists()
30-
splittedTxLists = make([][]*types.Transaction, 0)
31+
txs = poolContent.ToTxsByPriceAndNonce(p.chainID)
32+
splittedTxLists = make([]types.Transactions, 0)
3133
txBuffer = make([]*types.Transaction, 0, p.maxTransactionsPerBlock)
3234
gasBuffer uint64 = 0
3335
)
3436

35-
if p.shufflePoolContent {
36-
txLists = p.weightedShuffle(txLists)
37-
}
37+
for {
38+
tx := txs.Peek()
39+
if tx == nil {
40+
break
41+
}
42+
43+
// If the transaction is invalid, we simply ignore it.
44+
if err := p.validateTx(tx); err != nil {
45+
log.Debug("Invalid pending transaction", "hash", tx.Hash(), "error", err)
46+
metrics.ProposerInvalidTxsCounter.Inc(1)
47+
txs.Pop() // If this tx is invalid, ignore this sender's other txs in pool.
48+
continue
49+
}
3850

39-
for _, txList := range txLists {
40-
for _, tx := range txList {
41-
// If the transaction is invalid, we simply ignore it.
42-
if err := p.validateTx(tx); err != nil {
43-
log.Debug("Invalid pending transaction", "hash", tx.Hash(), "error", err)
44-
metrics.ProposerInvalidTxsCounter.Inc(1)
45-
break // If this tx is invalid, ingore this sender's other txs with larger nonce.
46-
}
47-
48-
// If the transactions buffer is full, we make all transactions in
49-
// current buffer a new splitted transaction list, and then reset the
50-
// buffer.
51-
if p.isTxBufferFull(tx, txBuffer, gasBuffer) {
52-
splittedTxLists = append(splittedTxLists, txBuffer)
53-
txBuffer = make([]*types.Transaction, 0, p.maxTransactionsPerBlock)
54-
gasBuffer = 0
55-
}
56-
57-
txBuffer = append(txBuffer, tx)
58-
gasBuffer += tx.Gas()
51+
// If the transactions buffer is full, we make all transactions in
52+
// current buffer a new splitted transaction list, and then reset the
53+
// buffer.
54+
if p.isTxBufferFull(tx, txBuffer, gasBuffer) {
55+
splittedTxLists = append(splittedTxLists, txBuffer)
56+
txBuffer = make([]*types.Transaction, 0, p.maxTransactionsPerBlock)
57+
gasBuffer = 0
5958
}
59+
60+
txBuffer = append(txBuffer, tx)
61+
gasBuffer += tx.Gas()
62+
63+
txs.Shift()
6064
}
6165

6266
// Maybe there are some remaining transactions in current buffer,
@@ -67,7 +71,7 @@ func (p *poolContentSplitter) split(poolContent rpc.PoolContent) [][]*types.Tran
6771

6872
// If the pool content is shuffled, we will only propose the first transactions list.
6973
if p.shufflePoolContent && len(splittedTxLists) > 0 {
70-
splittedTxLists = [][]*types.Transaction{splittedTxLists[0]}
74+
splittedTxLists = []types.Transactions{p.weightedShuffle(splittedTxLists)[0]}
7175
}
7276

7377
return splittedTxLists

‎proposer/pool_content_splitter_test.go

+22-7
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@ import (
66

77
"github.com/ethereum/go-ethereum/common"
88
"github.com/ethereum/go-ethereum/core/types"
9+
"github.com/ethereum/go-ethereum/crypto"
910
"github.com/ethereum/go-ethereum/rlp"
11+
"github.com/taikoxyz/taiko-client/bindings"
1012
"github.com/taikoxyz/taiko-client/pkg/rpc"
1113
"github.com/taikoxyz/taiko-client/testutils"
1214
)
1315

1416
func (s *ProposerTestSuite) TestPoolContentSplit() {
1517
// Gas limit is smaller than the limit.
16-
splitter := &poolContentSplitter{minTxGasLimit: 21000}
18+
splitter := &poolContentSplitter{
19+
chainID: s.RpcClient.L2ChainID,
20+
minTxGasLimit: 21000,
21+
}
1722

1823
splitted := splitter.split(rpc.PoolContent{
1924
common.BytesToAddress(testutils.RandomBytes(32)): {
@@ -24,7 +29,10 @@ func (s *ProposerTestSuite) TestPoolContentSplit() {
2429
s.Empty(splitted)
2530

2631
// Gas limit is larger than the limit.
27-
splitter = &poolContentSplitter{minTxGasLimit: 21000}
32+
splitter = &poolContentSplitter{
33+
chainID: s.RpcClient.L2ChainID,
34+
minTxGasLimit: 21000,
35+
}
2836

2937
splitted = splitter.split(rpc.PoolContent{
3038
common.BytesToAddress(testutils.RandomBytes(32)): {
@@ -42,6 +50,7 @@ func (s *ProposerTestSuite) TestPoolContentSplit() {
4250
s.NotEmpty(bytes)
4351

4452
splitter = &poolContentSplitter{
53+
chainID: s.RpcClient.L2ChainID,
4554
maxBytesPerTxList: uint64(len(bytes) - 1),
4655
minTxGasLimit: uint64(len(bytes) - 2),
4756
}
@@ -53,21 +62,27 @@ func (s *ProposerTestSuite) TestPoolContentSplit() {
5362
s.Empty(splitted)
5463

5564
// Transactions that meet the limits
56-
tx := types.NewTx(&types.LegacyTx{Gas: 21001})
65+
goldenTouchPriKey, err := crypto.HexToECDSA(bindings.GoldenTouchPrivKey[2:])
66+
s.Nil(err)
67+
68+
signer := types.LatestSignerForChainID(s.RpcClient.L2ChainID)
69+
tx1 := types.MustSignNewTx(goldenTouchPriKey, signer, &types.LegacyTx{Gas: 21001, Nonce: 1})
70+
tx2 := types.MustSignNewTx(goldenTouchPriKey, signer, &types.LegacyTx{Gas: 21001, Nonce: 2})
5771

58-
bytes, err = rlp.EncodeToBytes(tx)
72+
bytes, err = rlp.EncodeToBytes(tx1)
5973
s.Nil(err)
6074
s.NotEmpty(bytes)
6175

6276
splitter = &poolContentSplitter{
77+
chainID: s.RpcClient.L2ChainID,
6378
minTxGasLimit: 21000,
64-
maxBytesPerTxList: uint64(len(bytes) + 1),
79+
maxBytesPerTxList: uint64(len(bytes) + 1000),
6580
maxTransactionsPerBlock: 1,
66-
blockMaxGasLimit: tx.Gas() + 1,
81+
blockMaxGasLimit: tx1.Gas() + 1000,
6782
}
6883

6984
splitted = splitter.split(rpc.PoolContent{
70-
common.BytesToAddress(testutils.RandomBytes(32)): {"0": tx, "1": tx},
85+
bindings.GoldenTouchAddress: {"1": tx1, "2": tx2},
7186
})
7287

7388
s.Equal(2, len(splitted))

‎proposer/proposer.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func InitFromConfig(ctx context.Context, p *Proposer, cfg *Config) (err error) {
8888
log.Info("Protocol configs", "configs", p.protocolConfigs)
8989

9090
p.poolContentSplitter = &poolContentSplitter{
91+
chainID: p.rpc.L2ChainID,
9192
shufflePoolContent: cfg.ShufflePoolContent,
9293
maxTransactionsPerBlock: p.protocolConfigs.MaxTransactionsPerBlock.Uint64(),
9394
blockMaxGasLimit: p.protocolConfigs.BlockMaxGasLimit.Uint64(),
@@ -165,7 +166,7 @@ func (p *Proposer) ProposeOp(ctx context.Context) error {
165166
return fmt.Errorf("failed to fetch transaction pool content: %w", err)
166167
}
167168

168-
log.Info("Fetching L2 pending transactions finished", "length", pendingContent.ToTxLists().Len())
169+
log.Info("Fetching L2 pending transactions finished", "length", pendingContent.Len())
169170

170171
var commitTxListResQueue []*commitTxListRes
171172
for i, txs := range p.poolContentSplitter.split(pendingContent) {

‎version/verison.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package version
22

33
// Version info.
44
var (
5-
Version = "0.2.2"
5+
Version = "0.2.3"
66
Meta = "dev"
77
)
88

0 commit comments

Comments
 (0)
This repository has been archived.