Skip to content

Commit f03e25d

Browse files
committed
prevent key leak on timeout in seth.AnySyncedKey()
1 parent 0cafe19 commit f03e25d

File tree

2 files changed

+63
-12
lines changed

2 files changed

+63
-12
lines changed

seth/.changeset/v1.51.5.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Prevent key leak on timeout in `seth.AnySyncedKey()` and treat key as synced if nonce incremented at least by 1 or there's no pending nonce

seth/nonce.go

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,36 +131,63 @@ func (m *NonceManager) anySyncedKey() int {
131131
Interface("Address", m.Addresses[keyData.KeyNum]).
132132
Msg("Key selected")
133133
go func() {
134+
addr := m.Addresses[keyData.KeyNum]
135+
rpcTimeout := max(
136+
// Use retry delay as RPC timeout
137+
m.cfg.KeySyncRetryDelay.Duration(), 5*time.Second)
138+
139+
// Track the last known nonce across retries for recovery
140+
var lastKnownNonce uint64
141+
hasValidNonce := false
142+
134143
err := retry.Do(
135144
func() error {
136145
m.rl.Take()
137146
L.Trace().
138147
Interface("KeyNum", keyData.KeyNum).
139-
Interface("Address", m.Addresses[keyData.KeyNum]).
148+
Interface("Address", addr).
140149
Msg("Key is syncing")
141-
nonce, err := m.Client.Client.NonceAt(context.Background(), m.Addresses[keyData.KeyNum], nil)
142-
if err != nil {
143-
return errors.New(ErrNonce)
150+
151+
rpcCtx, rpcCancel := context.WithTimeout(context.Background(), rpcTimeout)
152+
defer rpcCancel()
153+
154+
// Check both pending and latest nonce to determine if key is available
155+
pendingNonce, pendingErr := m.Client.Client.PendingNonceAt(rpcCtx, addr)
156+
if pendingErr != nil {
157+
return errors.Wrap(pendingErr, ErrNonce)
158+
}
159+
latestNonce, latestErr := m.Client.Client.NonceAt(rpcCtx, addr, nil)
160+
if latestErr != nil {
161+
return errors.Wrap(latestErr, ErrNonce)
144162
}
145-
if nonce == keyData.Nonce+1 {
163+
164+
// Store for potential recovery use
165+
lastKnownNonce = latestNonce
166+
hasValidNonce = true
167+
168+
// Key is synced if there's no pending transaction (pending == latest)
169+
// OR if the nonce has incremented from what we expected
170+
if pendingNonce == latestNonce || latestNonce >= keyData.Nonce+1 {
146171
L.Trace().
147172
Interface("KeyNum", keyData.KeyNum).
148-
Uint64("Nonce", nonce).
149-
Interface("Address", m.Addresses[keyData.KeyNum]).
173+
Uint64("LatestNonce", latestNonce).
174+
Uint64("PendingNonce", pendingNonce).
175+
Interface("Address", addr).
150176
Msg("Key synced")
151177
m.SyncedKeys <- &KeyNonce{
152178
KeyNum: keyData.KeyNum,
153-
Nonce: nonce,
179+
Nonce: latestNonce,
154180
}
155181
return nil
156182
}
157183

158184
L.Trace().
159185
Interface("KeyNum", keyData.KeyNum).
160-
Uint64("Nonce", nonce).
161-
Int("Expected nonce", mustSafeInt(keyData.Nonce+1)).
162-
Interface("Address", m.Addresses[keyData.KeyNum]).
163-
Msg("Key NOT synced")
186+
Uint64("LatestNonce", latestNonce).
187+
Uint64("PendingNonce", pendingNonce).
188+
Uint64("ExpectedNonce", keyData.Nonce+1).
189+
Interface("Address", addr).
190+
Msg("Key NOT synced - has pending transaction")
164191

165192
return errors.New(ErrKeySync)
166193
},
@@ -169,6 +196,29 @@ func (m *NonceManager) anySyncedKey() int {
169196
)
170197
if err != nil {
171198
m.Client.Errors = append(m.Client.Errors, errors.New(ErrKeySync))
199+
200+
// NEVER leak the key - always return it to the pool
201+
var nonceToUse uint64
202+
if hasValidNonce {
203+
nonceToUse = lastKnownNonce
204+
L.Warn().
205+
Interface("KeyNum", keyData.KeyNum).
206+
Uint64("Nonce", nonceToUse).
207+
Interface("Address", addr).
208+
Msg("Key sync failed, returning key to pool with last known nonce")
209+
} else {
210+
// Fall back to the original nonce when key was checked out
211+
nonceToUse = keyData.Nonce
212+
L.Warn().
213+
Interface("KeyNum", keyData.KeyNum).
214+
Uint64("Nonce", nonceToUse).
215+
Interface("Address", addr).
216+
Msg("Key sync failed, returning key to pool with original nonce")
217+
}
218+
m.SyncedKeys <- &KeyNonce{
219+
KeyNum: keyData.KeyNum,
220+
Nonce: nonceToUse,
221+
}
172222
}
173223
}()
174224
return keyData.KeyNum

0 commit comments

Comments
 (0)