feat(indexer): add /debug/recent_transactions page#3435
Conversation
Nodes submit transactions to the chain "fire and forget": a successful submission only means the transaction was routed, not that it was included or had its intended effect. The transaction processor already observes the on-chain effect after a delay, but that outcome is only aggregated into a prometheus counter, leaving operators unable to see which specific transaction failed. Add an in-memory ring buffer of recently submitted transactions, surfaced on a new /debug/recent_transactions endpoint. Each entry records the txid, nonce, signer access key, method, reference block height, submission time, and observed outcome. The status is tracked across the full lifecycle: an entry is created at submission and updated in place once the existing observation completes, so an operator can correlate failures with the access key and nonce involved (relevant to the suspected out-of-order nonce rejections). The buffer is shared between the web server and the transaction processor and read directly by the handler, so the page works regardless of the node's running state.
…ubmitted-transactions
There was a problem hiding this comment.
Pull request overview
Adds operational debugging support to the node by tracking recently submitted NEAR transactions in-memory and exposing them via a new /debug/recent_transactions endpoint, enabling operators to correlate submission attempts with later observed outcomes (e.g., nonce/order issues).
Changes:
- Introduces an in-memory bounded ring buffer (
RecentTransactions) with status updates across the tx lifecycle. - Wires a shared
RecentTransactionsbuffer through node startup so it’s available to both the web server and the transaction processor. - Adds a new web debug endpoint
/debug/recent_transactionsto render the buffer contents.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/node/src/web.rs | Adds shared recent-transactions state to the web server and exposes /debug/recent_transactions. |
| crates/node/src/tests.rs | Updates test web server startup to provide a RecentTransactions buffer. |
| crates/node/src/run.rs | Creates and shares the RecentTransactions buffer between web server and indexer. |
| crates/node/src/indexer/tx_signer.rs | Exposes account_id() from TransactionSigner for recording metadata. |
| crates/node/src/indexer/tx_sender.rs | Records submissions/outcomes into the buffer and updates metric labeling accordingly. |
| crates/node/src/indexer/recent_transactions.rs | New module implementing the bounded buffer, statuses, formatting, and unit tests. |
| crates/node/src/indexer/real.rs | Threads the shared buffer into the real indexer transaction processor startup. |
| crates/node/src/indexer.rs | Exposes the new recent_transactions module. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Pull request overviewAdds a Changes:
Reviewed changesPer-file summary
FindingsNon-blocking (nits, follow-ups, suggestions):
Approved. |
/debug/recent_transactions page
Address PR review feedback: - Record the transaction signature alongside each entry (issue #2890's acceptance criteria lists txid, nonce, signature). near_crypto::Signature is captured in submit_tx before the signed transaction is moved into the RPC handler, carried through SubmittedTxMetadata, and rendered via Display (ed25519:<base58>) on the debug page. It is None on the SubmitFailed path. - Consolidate the two record-construction sites behind SubmittedTransaction::submitting / submit_failed, taking a SignerContext so the shared signer fields are written in one place and no longer cloned per branch. - Use safe arithmetic for the record-id counter and scan update_status from the back, where the just-recorded entry lives.
|
@claude review |
Pull request overviewAdds a Changes:
Reviewed changesPer-file summary
FindingsNon-blocking (nits, follow-ups, suggestions):
✅ Approved |
Address PR review feedback: - Add RecentTransactions::snapshot(), which clones the entries under the lock so the web handler can format the page after releasing it. Holding the std::sync::Mutex across the format! could block the transaction processor's record_submitted/update_status writes. - Move the human-readable per-entry rendering into a Display impl on SubmittedTransaction plus a render() helper, so the page's wire format is no longer expressed through a Debug impl. - Reword the signature-clone comment in submit_tx: the move happens when the transaction is passed into rpc_handler.submit_tx, not when entering submit_tx.
The `no-use-in-fn` ast-grep lint (cargo make check-all-fast) rejects `use` declarations inside function bodies. Move the `std::fmt::Write` import used by `render()` up to the module-level imports, matching requests/debug.rs.
…ffer Replace update_status's reverse linear scan with O(1) arithmetic id-to-index lookup. Ids come from a monotonic counter and the buffer is strict FIFO, so the live ids are always a contiguous range and an id maps to a deque position by subtraction (id - (next_id - len)); no auxiliary index needed. The previous scan walked past every submission made during the 10s observe timeout, not the O(1) it claimed. Also: fold the four built-transaction fields into a single Option<SubmittedTxMetadata> (enforcing the all-or-nothing invariant in the type), derive Display on both types instead of a hand-written impl, consolidate the two constructors through a private new(), bump the retained-entry cap to 2000, and derive Default. Add tests for buried/evicted/never-issued/empty-buffer lookups.
…ubmitted-transactions
…g broadcast Document, in the recent_transactions module doc, why this page reads the shared buffer directly instead of using the debug-request broadcast like the other debug pages: the broadcast is only subscribed in the Running state, whereas the transaction processor runs across all states, so reading the buffer directly keeps the page available (e.g. while Initializing) when submission failures most need inspecting. Point the web.rs field comment at that rationale.
|
@claude review |
| pub fn record_submitted(&mut self, transaction: SubmittedTransaction) -> TransactionRecordId { | ||
| let id = TransactionRecordId(self.next_id); | ||
| self.next_id = self.next_id.wrapping_add(1); | ||
|
|
Pull request overviewAdds a Since the last review pass, this iteration:
Changes:
Reviewed changesPer-file summary
FindingsNon-blocking (nits, follow-ups, suggestions):
✅ Approved |
netrome
left a comment
There was a problem hiding this comment.
Started reviewing, will continue next week.
There was a problem hiding this comment.
Haven't gone deep yet, but given that this is a data structure for the recent_transactions page it feels weird to have it live under the indexer. It feels like this belongs closer to the web module - potentially as a submodule of it, or somewhere adjacent.
Closes #2890
Nodes submit transactions to the chain "fire and forget": a successful submission only means the transaction was routed, not that it was included or had its intended effect. The transaction processor already observes the on-chain effect after a delay, but that outcome is only aggregated into a prometheus counter, so an operator can't see which specific transaction failed.
This adds a
/debug/recent_transactionspage backed by an in-memory ring buffer. Each entry records the txid, nonce, signer access key, method, reference block height, submission time, and observed outcome. Status is tracked across the full lifecycle (Submitting→Executed/NotExecuted/Unknown/SubmitFailed), so failures can be correlated with the access key and nonce involved — relevant to the suspected out-of-order nonce rejections.The buffer is shared between the web server and the transaction processor and read directly by the handler, so the page works regardless of the node's running state.