feat(sequencer): implement get_pending_nonce for sequencer API#1073
feat(sequencer): implement get_pending_nonce for sequencer API#1073
get_pending_nonce for sequencer API#1073Conversation
Fraser999
left a comment
There was a problem hiding this comment.
No blockers - just one point about a potential performance issue.
| let inner = self.inner.read().await; | ||
| let mut nonce = None; | ||
| for (tx, priority) in inner.iter() { | ||
| let sender = Address::from_verification_key(tx.verification_key()); |
There was a problem hiding this comment.
I think this could be overly costly in terms of execution/cycles, but I was hoping to memoize the address inside a newtype wrapper for VerificationKey as part of #1079, and that should help.
SuperFluffy
left a comment
There was a problem hiding this comment.
A few comments I would like to see addressed prior to approval.
| }; | ||
|
|
||
| let address = Address::try_from_raw(&address) | ||
| .map_err(|e| Status::invalid_argument(format!("invalid address: {e}")))?; |
There was a problem hiding this comment.
Please emit an event here, logging the entire error source chain.
There was a problem hiding this comment.
the error returned from Address::try_from_raw is IncorrectAddressLength so that's the only possible error, can add a trace log
There was a problem hiding this comment.
It's the only error at this point. This might or might not change in the future. Assume you have no knowledge about the error.
|
|
||
| // nonce wasn't in mempool, so just look it up from storage | ||
| let snapshot = self.storage.latest_snapshot(); | ||
| let nonce = snapshot.get_account_nonce(address).await.map_err(|e| { |
There was a problem hiding this comment.
Event logging the error source chain.
| astria.primitive.v1.Address address = 1 [(google.api.field_behavior) = REQUIRED]; | ||
| } | ||
|
|
||
| message PendingNonce { |
There was a problem hiding this comment.
| message PendingNonce { | |
| message GetPendingNonceResponse |
|
|
||
| let request = request.into_inner(); | ||
| let Some(address) = request.address else { | ||
| return Err(Status::invalid_argument( |
| /// required so that `BasicMempool::iter_mut()` can be called. | ||
| pub(crate) async fn inner(&self) -> tokio::sync::MutexGuard<'_, BasicMempool> { | ||
| self.inner.lock().await | ||
| pub(crate) async fn inner(&self) -> tokio::sync::RwLockWriteGuard<'_, BasicMempool> { |
There was a problem hiding this comment.
| pub(crate) async fn inner(&self) -> tokio::sync::RwLockWriteGuard<'_, BasicMempool> { | |
| pub(crate) async fn write(&self) -> tokio::sync::RwLockWriteGuard<'_, BasicMempool> { |
|
|
||
| /// returns the pending nonce for the given address, | ||
| /// if it exists in the mempool. | ||
| pub(crate) async fn pending_nonce(&self, address: &Address) -> Option<u32> { |
There was a problem hiding this comment.
insturment this method, injecting the address as a span attribute
SuperFluffy
left a comment
There was a problem hiding this comment.
Please change the span events to levels that we will actually observe during normal operation. It is very interesting to know why a particular request was rejected. As it stands, we will never know that.
|
|
||
| let request = request.into_inner(); | ||
| let Some(address) = request.address else { | ||
| trace!("required field address was not set",); |
There was a problem hiding this comment.
info! level. We want to know what happened when a request is rejected.
There was a problem hiding this comment.
that seems really high considering these endpoints could potentially be hit a lot, was concerned about spamming the logs, can change tho
There was a problem hiding this comment.
Don't be. The job of a service is to give a good idea about what's going on during its execution. Obviously within reason - you don't want to emit events in a hot loop.
It's the job of the observability platform to make sense of the traces/events that it collects by either dropping them or displaying them such that they are easy to understand.
In this case here I am not at all concerned with these logs.
| }; | ||
|
|
||
| let address = Address::try_from_raw(&address).map_err(|e| { | ||
| trace!( |
There was a problem hiding this comment.
| trace!( | |
| info!( |
| // nonce wasn't in mempool, so just look it up from storage | ||
| let snapshot = self.storage.latest_snapshot(); | ||
| let nonce = snapshot.get_account_nonce(address).await.map_err(|e| { | ||
| trace!( |
There was a problem hiding this comment.
This strikes me as a critical error that we definitely want to notify on.
| trace!( | |
| error!( |
## Summary The mempool was refactored to reduce the visibility, count and scope of acquired lock guards, and to be implemented in terms of a single collection rather than two which needed synchronized. ## Background The mempool has been implemented to be functionally correct, but can benefit from a refactor to improve safety, performance and complexity (by avoiding synchronizing two collections). **NOTE**: This PR is based on top of #1073. Only the final two commits comprise the new work in this PR. ## Changes - Having reduced the `BasicMempool` to only holding a single collection, there seemed little point to retaining it, so the `Mempool` now just holds the naked priority queue. - Changed from using a `DoublePriorityQueue` to a `PriorityQueue` since we don't need the extra functionality, and the latter is slightly more performant. - All mempool methods now take `&self` rather than `&mut self`. - The `RwLock` is no longer locked and unlocked in a tight loop in the `insert_all` and `remove_all` methods - `TransactionPriority` has been updated to only hold the nonce diff. There was a subtle bug in the previous impl since the derived `PartialEq` and `Eq` didn't correspond to the hand-rolled impls of `PartialOrd` and `Ord`. This has been fixed and further tests added. - `SignedTransaction` is internally held in an `Arc` now and passed in an `Arc` when executing by the app to reduce the number of clones. We could potentially look to extend this across more of the sequencer code for the same reason (not in this PR) - In [the final commit](fd4402b), I changed how `update_mempool_after_finalization` was implemented, so that the mempool no longer has to expose a lock guard. I think it's generally much safer to not expose the lock or a guard on the lock in a non-private API. As well as moving the implementation inside mempool, I added a temporary in-mem cache of current account nonces, since it seems that there could be many txs all under the same account. As already noted, this method could be problematic due to holding the mempool lock for an extended duration, so this might reduce the risk. ## Testing Unit tests extended/added. ## Related Issues Closes #1083. --------- Co-authored-by: elizabeth <elizabethjbinks@gmail.com> Co-authored-by: noot <36753753+noot@users.noreply.github.com>
Summary
implement
get_pending_noncefor sequencer API, which returns the highest nonce in the mempool (if the address has txs in the mempool), otherwise just returns the nonce in the latest state.Background
useful for tooling and UX
Changes
get_pending_noncefor sequencer APIpending_noncein the mempool, which returns the highest nonce it has for that address if it existsTesting
unit tests
Related Issues
closes #1070