Skip to content

Limit invalid tx size - #1090

Open
shargon wants to merge 2 commits into
master-n3from
limit-invalid-tx-size
Open

Limit invalid tx size#1090
shargon wants to merge 2 commits into
master-n3from
limit-invalid-tx-size

Conversation

@shargon

@shargon shargon commented Jul 18, 2026

Copy link
Copy Markdown
Member

This pull request refactors how invalid transactions are tracked in the consensus process by introducing a custom FIFO cache and updating all related usages. The main goal is to improve memory management and provide a more structured way to handle invalid transactions. The changes affect both the core consensus logic and related tests.

Invalid transaction tracking improvements:

  • Replaced the Dictionary<UInt256, HashSet<ECPoint>> previously used for InvalidTransactions with a custom FIFO cache class (InvalidCache) that holds UnvalidTxCacheItem records, improving memory management and eviction of old invalid entries. [1] [2] [3]
  • Updated all usages of InvalidTransactions to work with the new cache structure, including accessing the .Value property of UnvalidTxCacheItem and adding new items in the correct format. [1] [2] [3]

Codebase maintenance:

  • Added the necessary using Neo.IO.Caching; directive to support the new cache implementation.

@github-actions github-actions Bot added the N3 label Jul 18, 2026
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.63636% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.96%. Comparing base (1437e69) to head (0cb5aa2).

Files with missing lines Patch % Lines
...FTPlugin/Consensus/ConsensusContext.MakePayload.cs 0.00% 2 Missing ⚠️
...DBFTPlugin/Consensus/ConsensusService.OnMessage.cs 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           master-n3    #1090      +/-   ##
=============================================
+ Coverage      50.94%   50.96%   +0.01%     
=============================================
  Files            284      284              
  Lines          16658    16663       +5     
  Branches        2137     2137              
=============================================
+ Hits            8487     8492       +5     
  Misses          7605     7605              
  Partials         566      566              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@roman-khimov roman-khimov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise the limit is OK.

@@ -221,10 +221,10 @@ private void OnChangeViewReceived(ExtensiblePayload payload, ChangeView message)
foreach (UInt256 hash in message.RejectedHashes)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the problems of this mechanism in general is that this set is completely disconnected from the proposal. Given that MemoryPoolMaxTransactions is usually less than ushort.MaxValue malicious node can subvert the mechanism completely by sending a full set of random hashes that will wipe out any existing ones.

My suggestion is to revert #984. Another option could be filtering hashes through PrepareRequest set of hashes if it's received and ignoring it completely if it's not.

@vncoelho

Copy link
Copy Markdown
Member

Lgtm @shargon ,
I will test performance.

@shargon

shargon commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

My suggestion is to revert #984. Another option could be filtering hashes through PrepareRequest set of hashes if it's received and ignoring it completely if it's not.

Merge this to fix the issue, and discuss the revert later?

@roman-khimov

Copy link
Copy Markdown
Contributor

This is still relevant:

malicious node can subvert the mechanism completely by sending a full set of random hashes that will wipe out any existing ones

We need to either fix it by filtering through the proposal or just drop the mechanism completely. I'm ok both ways.

hashset.Value.Add(pubkey);
else
context.InvalidTransactions.Add(hash, [pubkey]);
context.InvalidTransactions.Add(new ConsensusContext.UnvalidTxCacheItem(hash, new HashSet<ECPoint> { pubkey }));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] Every hash in message.RejectedHashes is inserted into InvalidTransactions with no check that it is in the mempool or the current PrepareRequest. ChangeView.Deserialize accepts up to ushort.MaxValue hashes, which is larger than the new capacity (MemoryPoolMaxTransactions, default 50_000). FIFOCache evicts the oldest key on Add once full (Cache.AddInternal), and FIFO OnAccess is a no-op, so later spam stays and earlier real votes disappear. After eviction, EnsureMaxBlockLimitation (TryGet + hashset.Value.Count > F) no longer skips that tx, so the primary can include it again. Honest backups still reject it (AddTransactionRequestChangeView), producing repeated view changes — the stall #984 was meant to stop. One Byzantine validator can do this with a single TxInvalid/TxRejectedByPolicy ChangeView (NewViewNumber only has to be higher than that validator's last CV). Safety (commit/prepare of an invalid tx) is unchanged; the bounded cache is what makes wipe-by-eviction possible (the old Dictionary grew instead of dropping votes). LRU would not save a 50k+ flood of new keys either.

Suggestion: Do not cache hashes that are not in the mempool and/or the current proposal (ignore RejectedHashes entirely if no PrepareRequest is in hand). Also cap accepted RejectedHashes length to what honest nodes send (one hash per CV). Capacity can stay at MemoryPoolMaxTransactions once admission is filtered, because MemPool_TransactionRemoved already Removes pool hashes.


Assert.AreSame(changeViewPayload, context.ChangeViewPayloads[2]);
CollectionAssert.Contains(context.InvalidTransactions[rejectedHash].ToArray(), context.Validators[2]);
CollectionAssert.Contains(context.InvalidTransactions[rejectedHash].Value.ToArray(), context.Validators[2]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The only test update is InvalidTransactions[rejectedHash].Value after one TxInvalid ChangeView. Nothing fills the cache to MemoryPoolMaxTransactions, asserts TryGet false after FIFO wrap, or drives EnsureMaxBlockLimitation to skip a tx once Value.Count > F. The skip path in ConsensusContext.MakePayload.cs:87-88 is therefore untested, as is the eviction behavior this PR exists to add.

Suggestion: Add a unit test that constructs InvalidCache (or a ConsensusContext) with a tiny capacity, inserts capacity+1 distinct hashes, and checks the oldest is gone and a later TryGet/EnsureMaxBlockLimitation still skips a hash with > F votes. A second case should show a spam flood of unknown hashes evicting a still-in-mempool invalid tx if admission stays unfiltered.


public sealed partial class ConsensusContext : IDisposable, ISerializable
{
public record UnvalidTxCacheItem(UInt256 Key, HashSet<ECPoint> Value);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] UnvalidTxCacheItem is a typo for Invalid. The record and InvalidCache are also public nested types on ConsensusContext, which leaks a cache implementation detail from a type that is already a large public surface.

Suggestion: Rename to InvalidTxCacheItem. Make both nested types private (or file-scoped) if callers only need InvalidTransactions lookup/add/remove.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] UnvalidTxCacheItem is a typo for Invalid. The record and InvalidCache are also public nested types on ConsensusContext, which leaks a cache implementation detail from a type that is already a large public surface.

Suggestion: Rename to InvalidTxCacheItem. Make both nested types private (or file-scoped) if callers only need InvalidTransactions lookup/add/remove.

@copilot fix it

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants