-
Notifications
You must be signed in to change notification settings - Fork 13
Add nonce-aware transaction pool #971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 54 commits
9d1f22c
ea8fa9d
7745740
7e1abcc
690170c
a74f178
c6320f6
45b8bb9
5967088
17bbdb8
2127a6f
8e5e010
7bf5129
82aec02
b43115b
9278c55
1060813
57c79c4
8f50460
de58a9e
9cce00c
24a6b3f
b535dc1
54fd7d3
9c6f6c5
4019174
328ccf4
d1ab394
d1db5d8
8a9053c
c705722
ad71012
7303ab9
b04c8ed
82306fe
8cbdfce
46cf137
ec791ad
f001141
8b3b742
fb426a5
968afb1
ac59fc4
7123a4e
59383db
17417b0
32a1f15
ab7fa95
294c631
60b0bb6
7e9f185
863632a
14a7eb8
0acad72
be128ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| package requester | ||
|
|
||
| import ( | ||
| "sync" | ||
|
|
||
| gethCommon "github.com/ethereum/go-ethereum/common" | ||
| "github.com/onflow/flow-go/fvm/evm" | ||
| "github.com/onflow/flow-go/fvm/evm/offchain/query" | ||
| flowGo "github.com/onflow/flow-go/model/flow" | ||
|
|
||
| "github.com/onflow/flow-evm-gateway/metrics" | ||
| "github.com/onflow/flow-evm-gateway/storage" | ||
| "github.com/onflow/flow-evm-gateway/storage/pebble" | ||
| ) | ||
|
|
||
| // NonceView reads EOA nonces at a single, fixed EVM state (one built block | ||
| // view). The mempool reads many EOAs' nonces from one view per flush tick | ||
| // rather than rebuilding the (expensive) view per address. It is an interface | ||
| // so tests can fake it without constructing a real query.View. | ||
| type NonceView interface { | ||
| // GetNonce returns the EOA's account nonce (the next nonce to use) at this | ||
| // view's state. Named GetNonce — not GetNextNonce — because this interface is | ||
| // satisfied directly by flow-go's query.View, whose method is GetNonce. | ||
| GetNonce(address gethCommon.Address) (uint64, error) | ||
| } | ||
|
|
||
| // NonceProvider returns the next nonce of the given EOA address. The transaction | ||
| // mempool uses it to determine the expected next nonce. | ||
| type NonceProvider interface { | ||
| // GetNextNonce returns the account nonce of the given EOA — its transaction | ||
| // count, i.e. the next nonce the EOA should use (matches eth_getTransactionCount). | ||
| // | ||
| // A non-nil error represents an EXCEPTION, not an expected condition: | ||
| // the underlying read is a local state-index lookup that should not | ||
| // fail under normal operation. Callers must therefore treat an error | ||
| // as a hard failure (reject the transaction / abort the operation) | ||
| // rather than a routine, recoverable condition to swallow. | ||
| GetNextNonce(address gethCommon.Address) (uint64, error) | ||
|
|
||
| // GetBlockView returns a NonceView over the latest indexed EVM state. A | ||
| // non-nil error is an EXCEPTION, same contract as GetNextNonce. | ||
| GetBlockView() (NonceView, error) | ||
| } | ||
|
|
||
| // LocalNonceProvider reads the EOA nonce from the latest height of the | ||
| // local state index. It caches the built block view and reuses it while the | ||
| // indexed height is unchanged (see GetBlockView). | ||
| type LocalNonceProvider struct { | ||
| chainID flowGo.ChainID | ||
| registerStore *pebble.RegisterStorage | ||
| blocks storage.BlockIndexer | ||
| collector metrics.Collector | ||
|
|
||
| // mu guards only the cached-view slot below (its read and update), NOT the | ||
| // expensive view build in GetBlockView. Note the cached view itself is shared | ||
| // across reads; callers that read it concurrently must still serialize (the | ||
| // mempool does, via its queueMux). | ||
| mu sync.Mutex | ||
| cachedView NonceView | ||
| cachedHeight uint64 | ||
| } | ||
|
|
||
| var _ NonceProvider = &LocalNonceProvider{} | ||
|
|
||
| func NewLocalNonceProvider( | ||
| chainID flowGo.ChainID, | ||
| registerStore *pebble.RegisterStorage, | ||
| blocks storage.BlockIndexer, | ||
| collector metrics.Collector, | ||
| ) *LocalNonceProvider { | ||
| return &LocalNonceProvider{ | ||
| chainID: chainID, | ||
| registerStore: registerStore, | ||
| blocks: blocks, | ||
| collector: collector, | ||
| } | ||
| } | ||
|
|
||
| // GetBlockView returns a NonceView over the latest indexed EVM height. The view | ||
| // is cached and reused while the indexed height is unchanged, so a burst of | ||
| // reads within one block — many Add calls, or a collectDueBatches pass — builds | ||
| // the (expensive) view only once. It is rebuilt when a new block is indexed. | ||
| // Reuse is safe because an EOA's on-chain nonce cannot change without a new | ||
| // block being indexed. | ||
| func (p *LocalNonceProvider) GetBlockView() (NonceView, error) { | ||
| height, err := p.blocks.LatestEVMHeight() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Fast path: reuse the cached view for this indexed height. Only the cache | ||
| // read (and the update below) is locked — the expensive view build runs | ||
| // OUTSIDE the lock. A concurrent miss may build the view more than once for | ||
| // the same height, which is wasteful but correct (same height => same view) | ||
| // and does not occur under the mempool's serialized (queueMux) access. | ||
| p.mu.Lock() | ||
| if p.cachedView != nil && p.cachedHeight == height { | ||
| view := p.cachedView | ||
| p.mu.Unlock() | ||
| p.collector.NonceViewCache(true) | ||
| return view, nil | ||
| } | ||
| p.mu.Unlock() | ||
|
|
||
| p.collector.NonceViewCache(false) | ||
|
|
||
| viewProvider := query.NewViewProvider( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't have to lock the nonce provider when querying the view. We only need to lock when reading and updating the cached view.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. k done - I also added a few additional metrics to track the EOA based cache (hits vs misses etc.) to help debug on testnet and mainnet. |
||
| p.chainID, | ||
| evm.StorageAccountAddress(p.chainID), | ||
| p.registerStore, | ||
| NewOverridableBlocksProvider(p.blocks, p.chainID, nil), | ||
| blockGasLimit, | ||
| ) | ||
|
|
||
| view, err := viewProvider.GetBlockView(height) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| p.mu.Lock() | ||
| p.cachedView = view | ||
| p.cachedHeight = height | ||
| p.mu.Unlock() | ||
|
|
||
| return view, nil | ||
| } | ||
|
|
||
| func (p *LocalNonceProvider) GetNextNonce(address gethCommon.Address) (uint64, error) { | ||
| view, err := p.GetBlockView() | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
|
|
||
| return view.GetNonce(address) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.