Skip to content

Conversation

@vladjdk
Copy link
Member

@vladjdk vladjdk commented Sep 22, 2025

Description

This PR adds the tracking of priority transactions via TxTracker, a local queue. This is populated by checking for temporary rejections (such as in the case of nonce gaps or underpriced tx), and adding it to the TxTracker if that is the case. This is a separate state and worker that runs in a goroutine and periodically (initially after 10s) reevaluates its contents. No longer valid txs are dropped, newly valid txs are queued, and the rest remain in the local queue.

If the home directory is configured and not blank, then the contents of the TxTracker are journaled and persisted to disk (by default in ~/.evmd/data/txpool/transactions.rlp). This is picked up between node restarts so that those local txs are not lost.

This PR also adds the 4 new fields (locals, no-locals, journal, rejournal) that is used by TxTracker to toml.go and server flags under evm.mempool. The configureEVMMempool/GetLegacyPoolConfig helpers have also been updated to make use of the toml and/or cli flag values.

Closes: #500


Author Checklist

I have...

  • tackled an existing issue or discussed with a team member
  • left instructions on how to review the changes
  • targeted the main branch

Comment on lines +147 to +155
for _, txs := range all {
for _, tx := range txs {
if err = rlp.Encode(replacement, tx); err != nil {
replacement.Close()
return err
}
}
journaled += len(txs)
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
for _, txs := range all {
for _, tx := range txs {
if err = rlp.Encode(replacement, tx); err != nil {
replacement.Close()

Check warning

Code scanning / CodeQL

Writable file handle closed without error handling Warning

File handle may be writable as a result of data flow from a
call to OpenFile
and closing it may result in data loss upon failure, which is not handled explicitly.

Copilot Autofix

AI about 9 hours ago

To correctly handle potential data loss or errors that occur upon closing a writable file handle, we must check and handle the error returned from replacement.Close() in all places it is called. Specifically:

  • In the loop on line 158, after a failed rlp.Encode(replacement, tx), the code calls replacement.Close() but ignores the error. We should close the file, check the return value, and if an error occurs, wrap it (or ensure it is reported). If both Encode and Close fail, we should not lose either error—often wrapping or combining is best.
  • Similarly, after the write loop on line 164, the call to replacement.Close() is made, but its error is ignored; we should check this and return the error if it happens.

The cleanest Go idiom is to set up a named return value, and when closing after an error, either wrap or combine the errors (e.g., use fmt.Errorf("...: %w", err)). In the normal path, if replacement.Close() fails, return its error.

No new imports are required, as fmt.Errorf is already available.

All changes occur in mempool/txpool/locals/journal.go, in the rotate function.


Suggested changeset 1
mempool/txpool/locals/journal.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/mempool/txpool/locals/journal.go b/mempool/txpool/locals/journal.go
--- a/mempool/txpool/locals/journal.go
+++ b/mempool/txpool/locals/journal.go
@@ -155,13 +155,18 @@
 	for _, txs := range all {
 		for _, tx := range txs {
 			if err = rlp.Encode(replacement, tx); err != nil {
-				replacement.Close()
+				closeErr := replacement.Close()
+				if closeErr != nil {
+					return fmt.Errorf("failed to encode tx and failed to close file: encode error: %w, close error: %v", err, closeErr)
+				}
 				return err
 			}
 		}
 		journaled += len(txs)
 	}
-	replacement.Close()
+	if err := replacement.Close(); err != nil {
+		return fmt.Errorf("failed to close replacement journal file: %w", err)
+	}
 
 	// Replace the live journal with the newly generated one
 	if err = os.Rename(journal.path+".new", journal.path); err != nil {
EOF
@@ -155,13 +155,18 @@
for _, txs := range all {
for _, tx := range txs {
if err = rlp.Encode(replacement, tx); err != nil {
replacement.Close()
closeErr := replacement.Close()
if closeErr != nil {
return fmt.Errorf("failed to encode tx and failed to close file: encode error: %w, close error: %v", err, closeErr)
}
return err
}
}
journaled += len(txs)
}
replacement.Close()
if err := replacement.Close(); err != nil {
return fmt.Errorf("failed to close replacement journal file: %w", err)
}

// Replace the live journal with the newly generated one
if err = os.Rename(journal.path+".new", journal.path); err != nil {
Copilot is powered by AI and may make mistakes. Always verify output.
}
journaled += len(txs)
}
replacement.Close()

Check warning

Code scanning / CodeQL

Writable file handle closed without error handling Warning

File handle may be writable as a result of data flow from a
call to OpenFile
and closing it may result in data loss upon failure, which is not handled explicitly.

Copilot Autofix

AI about 9 hours ago

The best way to fix this issue is to explicitly check the error returned by replacement.Close() after all write operations in the journal rotation process. This involves modifying the code region around line 164 to assign the result of Close() to an error variable, and handle it appropriately (return it if it occurs and there’s no prior error). Since this code is currently after a loop of writes, we must ensure that a failure in Close is treated as a failed journal rotation. The fix should only change this portion of code, add no new functionality, retain all code structure, and avoid modifying file-level logic elsewhere.

This does not require new imports, but does require updating error assignment/handling in the rotate method.

Suggested changeset 1
mempool/txpool/locals/journal.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/mempool/txpool/locals/journal.go b/mempool/txpool/locals/journal.go
--- a/mempool/txpool/locals/journal.go
+++ b/mempool/txpool/locals/journal.go
@@ -161,7 +161,9 @@
 		}
 		journaled += len(txs)
 	}
-	replacement.Close()
+	if err := replacement.Close(); err != nil {
+		return err
+	}
 
 	// Replace the live journal with the newly generated one
 	if err = os.Rename(journal.path+".new", journal.path); err != nil {
EOF
@@ -161,7 +161,9 @@
}
journaled += len(txs)
}
replacement.Close()
if err := replacement.Close(); err != nil {
return err
}

// Replace the live journal with the newly generated one
if err = os.Rename(journal.path+".new", journal.path); err != nil {
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +125 to +141
for sender, txs := range tracker.byAddr {
// Wipe the stales
stales := txs.Forward(tracker.pool.Nonce(sender))
for _, tx := range stales {
delete(tracker.all, tx.Hash())
}
numStales += len(stales)

// Check the non-stale
for _, tx := range txs.Flatten() {
if tracker.pool.Has(tx.Hash()) {
numOk++
continue
}
resubmits = append(resubmits, tx)
}
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
Comment on lines +145 to +148
for _, tx := range tracker.all {
addr, _ := types.Sender(tracker.signer, tx)
rejournal[addr] = append(rejournal[addr], tx)
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
Comment on lines +150 to +155
for _, list := range rejournal {
// cmp(a, b) should return a negative number when a < b,
slices.SortFunc(list, func(a, b *types.Transaction) int {
return int(a.Nonce() - b.Nonce())
})
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
// layer was also initialized to spawn any goroutines required by the service.
func (tracker *TxTracker) Start() error {
tracker.wg.Add(1)
go tracker.loop()

Check notice

Code scanning / CodeQL

Spawning a Go routine Note

Spawning a Go routine may be a possible source of non-determinism
defer tracker.journal.close()
}
var (
lastJournal = time.Now()

Check warning

Code scanning / CodeQL

Calling the system time Warning

Calling the system time may be a possible source of non-determinism
if checkJournal {
// Lock to prevent journal.rotate <-> journal.insert (via TrackAll) conflicts
tracker.mu.Lock()
lastJournal = time.Now()

Check warning

Code scanning / CodeQL

Calling the system time Warning

Calling the system time may be a possible source of non-determinism
@almk-dev almk-dev changed the title wip: set up local journal feat(mempool): Add TxTracker support for handling temporary rejections Oct 24, 2025
@almk-dev almk-dev changed the title feat(mempool): Add TxTracker support for handling temporary rejections feat(mempool): Add TxTracker support for handling temporary rejections Oct 24, 2025
@almk-dev almk-dev marked this pull request as ready for review October 27, 2025 16:01
@vladjdk
Copy link
Member Author

vladjdk commented Oct 28, 2025

Could you add more information in the PR about the different effects of this PR? (i.e. new config that's available, the fact that we save a new file to disk, etc.)

@vladjdk
Copy link
Member Author

vladjdk commented Oct 28, 2025

Could we add a system and integration test for this as well?

@vladjdk
Copy link
Member Author

vladjdk commented Oct 28, 2025

I think we can also merge the logic in for #494 (comment) to make this fully functional.

@codecov
Copy link

codecov bot commented Nov 4, 2025

Codecov Report

❌ Patch coverage is 38.60590% with 229 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.30%. Comparing base (3225549) to head (a406eb1).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
mempool/txpool/locals/journal.go 15.05% 72 Missing and 7 partials ⚠️
rpc/backend/call_tx.go 28.97% 44 Missing and 32 partials ⚠️
mempool/txpool/locals/tx_tracker.go 74.74% 17 Missing and 8 partials ⚠️
server/server_app_options.go 0.00% 7 Missing and 6 partials ⚠️
mempool/mempool.go 55.55% 8 Missing and 4 partials ⚠️
server/start.go 0.00% 11 Missing ⚠️
mempool/txpool/locals/errors.go 41.66% 5 Missing and 2 partials ⚠️
mempool/txpool/legacypool/legacypool.go 50.00% 3 Missing and 1 partial ⚠️
server/config/config.go 33.33% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #646      +/-   ##
==========================================
- Coverage   64.96%   63.30%   -1.67%     
==========================================
  Files         317      320       +3     
  Lines       21604    22029     +425     
==========================================
- Hits        14036    13946      -90     
- Misses       6349     6630     +281     
- Partials     1219     1453     +234     
Files with missing lines Coverage Δ
rpc/backend/sign_tx.go 12.00% <ø> (-44.25%) ⬇️
server/config/opendb.go 0.00% <ø> (ø)
server/flags/flags.go 0.00% <ø> (ø)
server/config/config.go 29.65% <33.33%> (-17.07%) ⬇️
mempool/txpool/legacypool/legacypool.go 66.47% <50.00%> (-17.60%) ⬇️
mempool/txpool/locals/errors.go 41.66% <41.66%> (ø)
server/start.go 0.00% <0.00%> (ø)
mempool/mempool.go 65.87% <55.55%> (-10.43%) ⬇️
server/server_app_options.go 28.57% <0.00%> (-28.93%) ⬇️
mempool/txpool/locals/tx_tracker.go 74.74% <74.74%> (ø)
... and 2 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: support TxTracker to track priority transactions

3 participants