Skip to content

Commit 2dca825

Browse files
authored
Test concurrent feecurrency tx handling in tx pool (#2333)
Add test to verify lack of race conditions in txpool When handling fee currency transactions. Also fixes a race condition in istanbul core when accessing the current round state.
1 parent ff274ca commit 2dca825

File tree

5 files changed

+92
-11
lines changed

5 files changed

+92
-11
lines changed

consensus/istanbul/core/handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ func (c *core) Start() error {
3636
return err
3737
}
3838

39+
c.currentMu.Lock()
3940
c.current = roundState
41+
c.currentMu.Unlock()
4042
c.roundChangeSetV2 = newRoundChangeSetV2(c.current.ValidatorSet())
4143

4244
// Reset the Round Change timer for the current round to timeout.

e2e_test/e2e_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ func testRPCDynamicTxGasPriceWithoutState(t *testing.T, afterGingerbread, altern
732732
gasTipCap := big.NewInt(0).Mul(suggestedGasPrice, big.NewInt(2))
733733

734734
// Send one celo from external account 0 to 1 via node 0.
735-
tx, err := accounts[0].SendValueWithDynamicFee(ctx, accounts[1].Address, 1, feeCurrency, gasFeeCap, gasTipCap, network[0])
735+
tx, err := accounts[0].SendValueWithDynamicFee(ctx, accounts[1].Address, 1, feeCurrency, gasFeeCap, gasTipCap, network[0], 0)
736736
require.NoError(t, err)
737737

738738
// Wait for the whole network to process the transaction.
@@ -1044,3 +1044,66 @@ func TestGetFinalizedBlock(t *testing.T) {
10441044
// Check latest and finalzed block are the same
10451045
require.Equal(t, h.Hash(), h2.Hash())
10461046
}
1047+
1048+
// TestManyFeeCurrencyTransactions is intended to test that we don't have race conditions in the tx pool when handling
1049+
// fee currency transactions. It does this by submitting many fee currency transactions from 3 different goroutines over
1050+
// a period of roughly 5 seconds which with the configured block time of 1 second means that the transactions should
1051+
// span multiple block boundaries with the goal of ensuring that both runReorg and AddRemotes are being called
1052+
// concurrently in the txPool. This issue https://github.com/celo-org/celo-blockchain/issues/2318 is occurring somewhat
1053+
// randomly and could be the result of some race condition in tx pool handling for fee currency transactions. However
1054+
// this test seems to run fairly reliably with the race flag enabled, which seems to indicate that the problem is not a
1055+
// result of racy behavior in the tx pool.
1056+
func TestManyFeeCurrencyTransactions(t *testing.T) {
1057+
ac := test.AccountConfig(3, 3)
1058+
gingerbreadBlock := common.Big0
1059+
gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil)
1060+
require.NoError(t, err)
1061+
ec.Istanbul.BlockPeriod = 1
1062+
network, shutdown, err := test.NewNetwork(ac, gc, ec)
1063+
require.NoError(t, err)
1064+
defer shutdown()
1065+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*200)
1066+
defer cancel()
1067+
1068+
cUSD := common.HexToAddress("0x000000000000000000000000000000000000d008")
1069+
cEUR := common.HexToAddress("0x000000000000000000000000000000000000D024")
1070+
cREAL := common.HexToAddress("0x000000000000000000000000000000000000d026")
1071+
1072+
accounts := test.Accounts(ac.DeveloperAccounts(), gc.ChainConfig())
1073+
1074+
time.Sleep(2 * time.Second)
1075+
txsChan := make(chan []*types.Transaction, 3)
1076+
for nodeIndex := 0; nodeIndex < len(network); nodeIndex++ {
1077+
go func(nodeIndex int) {
1078+
txs := make([]*types.Transaction, 0, 3000)
1079+
for i := 0; i < 100; i++ {
1080+
for _, feeCurrency := range []*common.Address{&cUSD, &cEUR, &cREAL} {
1081+
baseFee, err := network[nodeIndex].WsClient.SuggestGasPriceInCurrency(ctx, feeCurrency)
1082+
require.NoError(t, err)
1083+
tip, err := network[nodeIndex].WsClient.SuggestGasTipCapInCurrency(ctx, feeCurrency)
1084+
require.NoError(t, err)
1085+
1086+
// Send one celo from external account 0 to 1 via node 0.
1087+
tx, err := accounts[nodeIndex].SendValueWithDynamicFee(ctx, accounts[nodeIndex].Address, 1, feeCurrency, baseFee.Add(baseFee, tip), tip, network[nodeIndex], 71000)
1088+
require.NoError(t, err)
1089+
txs = append(txs, tx)
1090+
time.Sleep(16 * time.Millisecond)
1091+
}
1092+
}
1093+
txsChan <- txs
1094+
}(nodeIndex)
1095+
}
1096+
1097+
allTxs := make([]*types.Transaction, 0, 3000*len(network))
1098+
count := 0
1099+
for txs := range txsChan {
1100+
allTxs = append(allTxs, txs...)
1101+
count++
1102+
if count == len(network) {
1103+
break
1104+
}
1105+
}
1106+
1107+
err = network.AwaitTransactions(ctx, allTxs...)
1108+
require.NoError(t, err)
1109+
}

ethclient/ethclient.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
546546
return (*big.Int)(&hex), nil
547547
}
548548

549-
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
549+
// SuggestGasPriceInCurrency retrieves the currently suggested gas price to allow a timely
550550
// execution of a transaction.
551551
func (ec *Client) SuggestGasPriceInCurrency(ctx context.Context, feeCurrency *common.Address) (*big.Int, error) {
552552
var hex hexutil.Big
@@ -566,6 +566,16 @@ func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
566566
return (*big.Int)(&hex), nil
567567
}
568568

569+
// SuggestGasTipCapInCurrency retrieves the currently suggested gas tip cap after 1559 to
570+
// allow a timely execution of a transaction.
571+
func (ec *Client) SuggestGasTipCapInCurrency(ctx context.Context, feeCurrency *common.Address) (*big.Int, error) {
572+
var hex hexutil.Big
573+
if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas", feeCurrency); err != nil {
574+
return nil, err
575+
}
576+
return (*big.Int)(&hex), nil
577+
}
578+
569579
// EstimateGas tries to estimate the gas needed to execute a specific transaction based on
570580
// the current pending state of the backend blockchain. There is no guarantee that this is
571581
// the true gas limit requirement as other transactions may be added or removed by miners,

test/account.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,13 @@ func (a *Account) SendCelo(ctx context.Context, recipient common.Address, value
7070
// SendCeloWithDynamicFee submits a value transfer transaction via the provided Node to send
7171
// celo to the recipient. The submitted transaction is returned.
7272
func (a *Account) SendCeloWithDynamicFee(ctx context.Context, recipient common.Address, value int64, gasFeeCap *big.Int, gasTipCap *big.Int, node *Node) (*types.Transaction, error) {
73-
return a.SendValueWithDynamicFee(ctx, recipient, value, nil, gasFeeCap, gasTipCap, node)
73+
return a.SendValueWithDynamicFee(ctx, recipient, value, nil, gasFeeCap, gasTipCap, node, 0)
7474
}
7575

76-
// SendValueWithDynamicFee submits a value transfer transaction via the provided Node to send
77-
// celo to the recipient. The submitted transaction is returned.
78-
func (a *Account) SendValueWithDynamicFee(ctx context.Context, recipient common.Address, value int64, feeCurrency *common.Address, gasFeeCap, gasTipCap *big.Int, node *Node) (*types.Transaction, error) {
76+
// SendValueWithDynamicFee submits a value transfer transaction via the provided Node to send celo to the recipient. The
77+
// submitted transaction is returned. Note that gasLimit is optional and if 0 is provided the estimate gas will be
78+
// called to determine the gas limit.
79+
func (a *Account) SendValueWithDynamicFee(ctx context.Context, recipient common.Address, value int64, feeCurrency *common.Address, gasFeeCap, gasTipCap *big.Int, node *Node, gasLimit uint64) (*types.Transaction, error) {
7980
var err error
8081
// Lazy set nonce
8182
if a.Nonce == nil {
@@ -101,7 +102,8 @@ func (a *Account) SendValueWithDynamicFee(ctx context.Context, recipient common.
101102
feeCurrency,
102103
gasFeeCap,
103104
gasTipCap,
104-
signer)
105+
signer,
106+
gasLimit)
105107

106108
if err != nil {
107109
return nil, err

test/node.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,14 +665,18 @@ func ValueTransferTransactionWithDynamicFee(
665665
gasFeeCap *big.Int,
666666
gasTipCap *big.Int,
667667
signer types.Signer,
668+
gasLimit uint64,
668669
) (*types.Transaction, error) {
669670
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
670671
defer cancel()
671672

672-
msg := ethereum.CallMsg{From: sender, To: &recipient, Value: value, FeeCurrency: feeCurrency}
673-
gasLimit, err := client.EstimateGas(ctx, msg)
674-
if err != nil {
675-
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
673+
if gasLimit == 0 {
674+
msg := ethereum.CallMsg{From: sender, To: &recipient, Value: value, FeeCurrency: feeCurrency}
675+
var err error
676+
gasLimit, err = client.EstimateGas(ctx, msg)
677+
if err != nil {
678+
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
679+
}
676680
}
677681
// Create the transaction and sign it
678682
rawTx := types.NewTx(&types.CeloDynamicFeeTx{

0 commit comments

Comments
 (0)