-
Notifications
You must be signed in to change notification settings - Fork 0
Description
## Problem # 1 - Nonce "holes"
The nonce-incrementing logic currently relies on Redis auto-expiration of keys to allow for the nonce to be "reset" to the last known nonce from the chain. However, this still presents a possibility that when a new block is formed, we end up with a "hole" in the nonce, resulting in a future transaction that may or may not ever make it into a block.
## Example
Current block: 10
Last known nonce @ block 10: 30
Redis cache: [] (empty)
* Post 3 txs, resulting in Redis cache updating to [1,2,3] -> nonces 30, 31, 32 used
* Before the Redis cache expires, a new block forms. Now the chain last known nonce is 32.
* Post a new transaction. Redis cache contains [1,2,3], so the next index is 4 -> nonce 32 + 4 - 1 = 35 --> results in "Future" transaction. This transaction may or may not ever be included in a block; it is entirely dependent on whether another transaction comes along at some point to use nonces 33 & 34 before the mortality era of this transaction expires. However, it is very likely that we will violate our own timeout logic before that happens. The result:
* We could hit our timeout logic, which would fail the job--but the pending transaction could still ultimately be included in a block. Depending on how many times this job has been attempted, it could either be erroneously flagged as a permanently failed job, or be retried later (not so bad; the retry should be a no-op)
## Possible solutions
### Solution # 1: Block number caching
One approach might be to cache the block number at which the current nonce was retrieved, and check it against the current block. If the current block number has changed since our last nonce check, clear the Redis nonce cache so that we start again at the current nonce.
#### Risks
This approach does not take into account pending extrinsics that were not included in the most recent block, and therefore have already allocated the current nonce (and possibly more). However, since this scenario would result in an immediate RPC error "Priority too low", it may be preferable to having to worry about a "Future" transaction whose ultimate disposition we don't know.
### Solution # 2: Handle Future Extrinsics
Another approach would be to explicity screen for "Future" status in the Extrinsic event stream Observable pipeline, and trigger some processing if it's encountered. The difficulty here would be to decide how, exactly, to handle this scenario, as the transaction may or may not still complete either before or after the timeout.
#### Risks
Complex solution, difficult to cover various cases/scenarios. I prefer solution # 1.
Problem # 2 - Redis cache timeout
A second problem is that the current default timeout for the Redis nonce keys is 2 seconds--much less than the current mainnet block time of 12 seconds. This means that it's much more likely to encounter a "Priority too low" RPC error due to re-used (but still pending) nonces, even if solution # 1 is implemented.
Example:
Current block: 10
Last known nonce @ block 10: 30
Redis cache: [] (empty)
- Post 3 txs, resulting in Redis cache updating to [1,2,3] -> nonces 30, 31, 32 used
- Before the next new block forms, the Redis 2s cache expires. Redis cache is now [] (or even [2,3])
- Post a new transaction. Redis cache contains [] (or [2,3]), so the next index is 1 -> nonce 30 + 1 - 1 = 30 --> results in "RPC Error: Priority too low"
Proposed solution
If, instead of auto-expiring the keys, we implement Solution # 1 above (block-number caching), and only clear the keys when the block number changes, it should solve this issue.