Skip to content

fix(sei-tendermint): restore proposal tx-key decode bounds (CON-334) - #4011

Merged
shemnon merged 8 commits into
mainfrom
shemnon/con-334-auditor-feedback-immunefi-80676-regression-of-txkeys
Aug 27, 2026
Merged

fix(sei-tendermint): restore proposal tx-key decode bounds (CON-334)#4011
shemnon merged 8 commits into
mainfrom
shemnon/con-334-auditor-feedback-immunefi-80676-regression-of-txkeys

Conversation

@shemnon

@shemnon shemnon commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Caps Proposal.tx_keys at decode with a wireguard maxCount derived from protocol maximums.
  • Restores the 32-byte hash check in TxHashFromProto so truncated or padded keys are refused at native conversion. Also adds wireguard check for max size on tx_keys
  • Adds wireguard at-cap / over-cap tests on consensus.Message and length tests for TxHashFromProto.

Test plan

  • go test ./sei-tendermint/internal/consensus/ -run 'TestSchemaForMessage_(AcceptsProposalTxKeysAtCap|RejectsProposalTxKeysOverCap)' -count=1
  • go test ./sei-tendermint/types/ -run TestTxHashFromProtoValidatesLength -count=1
  • go test ./sei-tendermint/internal/consensus/ ./sei-tendermint/types/ -count=1

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 26, 2026, 6:07 PM

Comment thread sei-tendermint/config/config.go Outdated
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.36%. Comparing base (be35b60) to head (23fe5f8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4011      +/-   ##
==========================================
- Coverage   61.30%   58.36%   -2.95%     
==========================================
  Files        2163     2170       +7     
  Lines      188732   182469    -6263     
==========================================
- Hits       115704   106493    -9211     
- Misses      62305    66259    +3954     
+ Partials    10723     9717    -1006     
Flag Coverage Δ
sei-chain-pr 79.95% <100.00%> (?)
sei-db 69.80% <ø> (-0.22%) ⬇️
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/internal/consensus/reactor.go 70.37% <ø> (-1.95%) ⬇️
...ndermint/proto/tendermint/types/types.wireguard.go 100.00% <100.00%> (ø)
sei-tendermint/types/mempool.go 86.20% <100.00%> (+8.42%) ⬆️
sei-tendermint/types/proposal.go 83.92% <ø> (ø)

... and 260 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.

seidroid[bot]
seidroid Bot previously requested changes Aug 25, 2026

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The restored tx-key validation (32-byte hashes, per-proposal count cap) is wired correctly through every decode path and well tested, but the 1000 default is far below the number of transactions a valid block can carry, so an honest large proposal would be rejected by every peer. Smaller notes on WAL-replay coupling to the live limit, the unregistered flag/env claim, and godoc style.

Findings: 1 blocking | 3 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.
  • 1 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • [suggestion] The PR description says the limit resolves through "defaults, file, env, flags", but no consensus.max-tx-keys-per-proposal flag is registered in AddNodeFlags (sei-tendermint/cmd/tendermint/commands/run_node.go), and viper's AutomaticEnv does not surface a key during Unmarshal unless it is bound or present in the file — so for a pre-existing config.toml that omits the key, neither the env var nor a flag can override it. Either register the flag (as consensus.gossip-tx-key-only and consensus.double-sign-check-height already are) or drop the env/flag claim.
  • [suggestion] The new godocs carry rationale rather than description, which AGENTS.md ("Godoc") rules out: the second paragraph of TxHashesListFromProto (sei-tendermint/types/mempool.go) explains why the count is checked first, and the added paragraph on confix.CheckValid explains the mechanism and the rejected alternative. Both belong as inline comments at the lines they justify; keep the godocs to what the function is.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread sei-tendermint/config/config.go Outdated
Comment thread sei-tendermint/internal/consensus/replay.go Outdated
@shemnon
shemnon requested a review from masih August 25, 2026 20:26
Comment thread sei-tendermint/config/config.go Outdated
Comment thread sei-tendermint/config/config.go Outdated
// Largest number of transaction hashes a proposal received from a peer may carry.
// The consensus channels cap a message at 4MB, which admits roughly 116,000 hashes,
// so the message size does not bound the decode on its own.
MaxTxKeysPerProposal int `mapstructure:"max-tx-keys-per-proposal"`

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.

@masih is the original intent just refusing a super long proposal from a peer? We recently added wireguard, should we use that?

I'm a bit worried that people can add different limits in their local nodes, then the consensus proposal may be accepted at some places but not others, we are back to "people may override consensus params" situation.

Maybe a better solution to set a limit using wireguard so people at least use the same limit. But then we need to make the limit reasonably large so we never hit it in reality.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

yes the original intent was to mitigate long proposals.

Either approach works. I would use wireguard if possible.

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.

Do you know what limit would work in production? Is 1000 far from what we will normally see?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I went with 10_000, which is the wireguard limit for commit signatures (txes are not validator sigs). But TXKey is just a hash, so we have the same level of memory risk as with validator signatures.

@masih masih Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

1K is plenty just now (thanks will for confirming).

Refuse more than 10,000 tx_keys on a gossiped proposal at scan, matching
Commit.signatures, so a 4MB consensus message cannot force an unbounded
TxKey slice. Native conversion still rejects hashes that are not 32 bytes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@shemnon
shemnon force-pushed the shemnon/con-334-auditor-feedback-immunefi-80676-regression-of-txkeys branch from 3684663 to 55047a7 Compare August 26, 2026 01:09
@shemnon

shemnon commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@seidroid review

seidroid[bot]
seidroid Bot previously requested changes Aug 26, 2026

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The approach was reworked from a config-driven limit to a wireguard max_count annotation plus a 32-byte length check in TxHashFromProto, which is a cleaner choke point and well covered by the new tests. The remaining blocker is that the 10,000 tx-key cap is not derived from any enforced block-level transaction limit, so an honest oversized proposal is rejected — and the sender disconnected — by every peer.

Findings: 1 blocking | 2 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.
  • 1 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • [suggestion] The PR title/description no longer match the diff: they describe consensus.max-tx-keys-per-proposal, a ConsensusConfig field, the confix.CheckValid change, and a test plan over ./sei-tendermint/config/ and ./sei-tendermint/scripts/confix/ — none of which exist in the current changes (the limit is now a wireguard.max_count proto annotation). Since this becomes the squash commit message, please rewrite it to describe the wire-schema approach and the actual test plan (./sei-tendermint/types/, ./sei-tendermint/internal/consensus/).
  • [suggestion] TxKey.tx_key still has no size bound in the wire schema (types.wireguard.go registers *TxKey as 1: {MaxCount: 1}), so the pre-decode layer accepts 10,000 arbitrarily-sized entries and the 32-byte invariant is only discovered after gogo unmarshals the whole message. Adding [(wireguard.max_size) = 32] to TxKey.tx_key would put both bounds at the same choke point and matches the convention already used for 32-byte hash fields in sei-tendermint/internal/autobahn/autobahn.proto (parent_hash, payload_hash, app_hash).

Comment thread sei-tendermint/proto/tendermint/types/types.proto Outdated
if dp == nil {
return TxHash{}, errors.New("nil data")
}
if len(dp.TxKey) != sha256.Size {

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.

Do we still need this check if we are going wireguard?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It was in the source PR, and since the linear issue was about porting missed fixes and it was the same source file, it fits.

If we take it out I think short TX Bytes will leave trailing zeros, and the ToProto always creates a full size key.

Since the only way it would ordinarily be mismatched is via corruption or someone else writing it, I consider it defense in depth, and hence prefer it stays.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

also, wireguard has no min size or exact size constraint. If there was a min_size we could drop the in-code check.

@cursor

cursor Bot commented Aug 26, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes consensus P2P decode and proposal validation paths; mis-tuned caps could reject valid proposals, but limits align with the existing 4MB message ceiling.

Overview
Tightens consensus proposal decoding so oversized or malformed transaction-key payloads are rejected before they reach native handling.

Proposal tx_keys now have wireguard limits: each key is capped at 32 bytes and the repeated field at ~116,508 entries, derived from the shared MaxConsensusMsgBytes (4MB) budget. The consensus reactor uses that same constant for channel maxMsgSize instead of a duplicate literal.

TxHashFromProto again requires exactly SHA-256 length keys; wrong lengths fail at conversion. Regenerated protobuf/wireguard artifacts and tests cover at-cap vs over-cap proposals and hash length validation.

Reviewed by Cursor Bugbot for commit 23fe5f8. Bugbot is set up for automated code reviews on this repo. Configure here.

@masih

masih commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

@seidroid review

seidroid[bot]
seidroid Bot previously requested changes Aug 26, 2026

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The 32-byte max_size bound on TxKey.tx_key and the length check in TxHashFromProto are correct and well tested, but the max_count = 2000 cap on Proposal.tx_keys is borrowed from Autobahn's MaxTxsPerBlock, which governs a code path that never emits this message — nothing bounds tx count on the classic consensus path, and the default mempool size (5000) is 2.5x the new cap, so an honest proposal can be rejected and its sender disconnected by every peer.

Findings: 1 blocking | 2 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.
  • 1 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • [suggestion] The PR description is out of sync with the diff: it says (wireguard.max_count) = 10000 (the value is now 2000, sourced from atypes.MaxTxsPerBlock) and does not mention the new (wireguard.max_size) = 32 bound on TxKey.tx_key. Since this becomes the squash commit message, please update both the summary and the test plan.
  • [suggestion] The new max_size = 32 bound on TxKey.tx_key has no wire-level test. consensusProposalMessageWithTxKeys builds &tmproto.TxKey{} with a nil TxKey, so every entry serializes to zero bytes and only MaxCount is exercised; mempool_test.go covers the native TxHashFromProto conversion, not the pre-decode layer. A TestSchemaForMessage_RejectsOversizedTxKey populating one entry with 33 bytes would pin the bound where it is actually enforced (protoutils/runtime/runtime.go:103).

Comment thread sei-tendermint/proto/tendermint/types/types.proto Outdated
…/con-334-auditor-feedback-immunefi-80676-regression-of-txkeys
Derive the max proposal numbers from other fixed constants.
@shemnon

shemnon commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@seidroid review

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The tx-key cap is now derived from the consensus channel's own 4MB reassembly budget (MaxConsensusMsgBytes / txKeyEntryBytes = 116508) and pinned by tests, which makes it unreachable-by-construction and closes the previous blocker; the 32-byte max_size bound and the TxHashFromProto length check are correct and now covered at both the wire and native layers. The only remaining issue is the stale PR description.

Findings: 0 blocking | 1 non-blocking | 0 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • [suggestion] The PR description is out of sync with the diff for the third revision running. The summary still claims (wireguard.max_count) = 10000, while the diff uses 116508; it also omits the new types.MaxConsensusMsgBytes / types.MaxTxKeysPerProposal constants in sei-tendermint/types/proposal.go, the maxMsgSize redefinition in internal/consensus/reactor.go, and the (wireguard.max_size) = 32 bound on TxKey.tx_key. The test plan also predates TestMaxTxKeysPerProposalExceedsEveryDeliverableProposal and TestSchemaForTxKey_{AcceptsAtCap,RejectsOverCap}. Since this becomes the squash commit message, please rewrite both sections to describe the transport-derived cap that actually landed.

@seidroid
seidroid Bot dismissed stale reviews from themself August 26, 2026 18:03

Superseded: latest AI review found no blocking issues.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 23fe5f8. Configure here.

// bound keeps the cap tight enough to still bound decode work.
func TestMaxTxKeysPerProposalExceedsEveryDeliverableProposal(t *testing.T) {
require.Greater(t, proposalSizeWithTxKeys(t, types.MaxTxKeysPerProposal), types.MaxConsensusMsgBytes)
require.LessOrEqual(t, proposalSizeWithTxKeys(t, types.MaxTxKeysPerProposal-1), types.MaxConsensusMsgBytes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tx-key cap test asserts inverted bound

Medium Severity

TestMaxTxKeysPerProposalExceedsEveryDeliverableProposal requires MaxTxKeysPerProposal keys to encode larger than MaxConsensusMsgBytes, but MaxTxKeysPerProposal is MaxConsensusMsgBytes / txKeyEntryBytes (integer division). That product is 16 bytes under the 4MB budget, so the new tightness test fails.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 23fe5f8. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The largest proposal permitted by these changes is indeed 16 bytes under, which means it will be correctly delivered,. As each tx hash is 36 bytes (including protobuf overhead) adding one more makes the message too large. Hence we meet the constraint of “scan cannot reject a proposal the transport would have delivered”

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.

I think the bot meant the tests should be:
require.LessOrEqual(....(Max))
require.Greater(...(Max+1))
?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's off by (at least) one tx hash because of the protobuf overhead of the proposal, which is what the limits are demonstrating. Maybe 19 bytes?

But the core question of "can anything sent over consensus be unmarshaled within the wireguard limit" errs to max being less than one hash larger than 4 Gib.

@shemnon
shemnon requested a review from wen-coding August 26, 2026 20:02
@shemnon
shemnon added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit edbe424 Aug 27, 2026
95 checks passed
@shemnon
shemnon deleted the shemnon/con-334-auditor-feedback-immunefi-80676-regression-of-txkeys branch August 27, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants