Skip to content

Commit 498311d

Browse files
make NewTxsEvent channel in simualted_beacon_api a buffered channel of capacity 15, to prevent it from deadlocking on nitro-tests
1 parent 39f0d7b commit 498311d

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

eth/catalyst/simulated_beacon_api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ type api struct {
3232

3333
func (a *api) loop() {
3434
var (
35-
newTxs = make(chan core.NewTxsEvent)
35+
// Arbitrum: we need to make newTxs a buffered channel because by the current design of simulated beacon
36+
// it would deadlock with this cycle a.sim.Commit() -> txpool.Sync() -> subpools reset -> update feeds (newTxs is one of the recievers)
37+
// Note: capacity of this channel should be the worst-case estimate of number of transactions all arriving simultaneously to the pool
38+
newTxs = make(chan core.NewTxsEvent, 15)
3639
sub = a.sim.eth.TxPool().SubscribeTransactions(newTxs, true)
3740
)
3841
defer sub.Unsubscribe()

eth/catalyst/simulated_beacon_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,64 @@ func TestSimulatedBeaconSendWithdrawals(t *testing.T) {
139139
}
140140
}
141141
}
142+
143+
func TestSimulatedBeaconAPIDeadlocksInExtremeConditions(t *testing.T) {
144+
txs := make(map[common.Hash]types.Transaction)
145+
146+
var (
147+
// testKey is a private key to use for funding a tester account.
148+
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
149+
150+
// testAddr is the Ethereum address of the tester account.
151+
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
152+
)
153+
154+
// short period (1 second) for testing purposes
155+
var gasLimit uint64 = 10_000_000
156+
genesis := core.DeveloperGenesisBlock(gasLimit, &testAddr)
157+
node, ethService, mock := startSimulatedBeaconEthService(t, genesis)
158+
_ = mock
159+
defer node.Close()
160+
161+
// simulated beacon api
162+
mockApi := &api{mock}
163+
go mockApi.loop()
164+
165+
chainHeadCh := make(chan core.ChainHeadEvent, 10)
166+
subscription := ethService.BlockChain().SubscribeChainHeadEvent(chainHeadCh)
167+
defer subscription.Unsubscribe()
168+
169+
// generate a bunch of transactions to overload simulated beacon api
170+
// current capacity of core.NewTxsEvent channel is 15, we send 30 txs
171+
signer := types.NewEIP155Signer(ethService.BlockChain().Config().ChainID)
172+
for i := 0; i < 30; i++ {
173+
tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
174+
if err != nil {
175+
t.Fatalf("error signing transaction, err=%v", err)
176+
}
177+
txs[tx.Hash()] = *tx
178+
179+
if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil {
180+
t.Fatal("SendTx failed", err)
181+
}
182+
}
183+
184+
includedTxs := make(map[common.Hash]struct{})
185+
186+
timer := time.NewTimer(12 * time.Second)
187+
for {
188+
select {
189+
case evt := <-chainHeadCh:
190+
for _, includedTx := range evt.Block.Transactions() {
191+
includedTxs[includedTx.Hash()] = struct{}{}
192+
}
193+
194+
// ensure all withdrawals/txs included. this will take two blocks b/c number of withdrawals > 10
195+
if len(includedTxs) == len(txs) {
196+
t.Fatal("all txs were included, the simulated beacon api did not deadlock")
197+
}
198+
case <-timer.C:
199+
return
200+
}
201+
}
202+
}

0 commit comments

Comments
 (0)