Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,561 changes: 781 additions & 780 deletions sei-cosmos/x/staking/types/staking.pb.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion sei-tendermint/internal/consensus/reactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const (
VoteChannel = p2p.ChannelID(0x22)
VoteSetBitsChannel = p2p.ChannelID(0x23)

maxMsgSize = 4194304 // 4MB; NOTE: keep larger than types.PartSet sizes.
maxMsgSize = types.MaxConsensusMsgBytes
)

// Reactor defines a reactor for the consensus service.
Expand Down
27 changes: 26 additions & 1 deletion sei-tendermint/internal/consensus/wireguard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
"github.com/sei-protocol/sei-chain/sei-tendermint/types"
)

const maxCommitSignatures = types.MaxVotesCount
const (
maxCommitSignatures = types.MaxVotesCount
maxProposalTxKeys = types.MaxTxKeysPerProposal
)

func marshal(t *testing.T, m gogoproto.Message) []byte {
t.Helper()
Expand Down Expand Up @@ -53,6 +56,28 @@ func consensusProposalMessage(lastCommit *tmproto.Commit, evidenceCommits ...*tm
}}
}

func consensusProposalMessageWithTxKeys(n int) *tmcons.Message {
txKeys := make([]*tmproto.TxKey, n)
for i := range txKeys {
txKeys[i] = &tmproto.TxKey{}
}
return &tmcons.Message{Sum: &tmcons.Message_Proposal{
Proposal: &tmcons.Proposal{Proposal: tmproto.Proposal{
TxKeys: txKeys,
}},
}}
}

func TestSchemaForMessage_AcceptsProposalTxKeysAtCap(t *testing.T) {
require.NoError(t, protoutils.Scan[*tmcons.Message](marshal(t,
consensusProposalMessageWithTxKeys(maxProposalTxKeys))))
}

func TestSchemaForMessage_RejectsProposalTxKeysOverCap(t *testing.T) {
require.Error(t, protoutils.Scan[*tmcons.Message](marshal(t,
consensusProposalMessageWithTxKeys(maxProposalTxKeys+1))))
}

func TestSchemaForMessage_AcceptsLastCommitAtCap(t *testing.T) {
require.NoError(t, protoutils.Scan[*tmcons.Message](marshal(t,
consensusProposalMessage(commitWith(maxCommitSignatures)))))
Expand Down
218 changes: 110 additions & 108 deletions sei-tendermint/proto/tendermint/types/types.pb.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions sei-tendermint/proto/tendermint/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ message Data {
}

message TxKey {
bytes tx_key = 1;
bytes tx_key = 1 [(wireguard.max_size) = 32 /* crypto.HashSize */];
}

// Vote represents a prevote, precommit, or commit vote from validators for
Expand Down Expand Up @@ -162,7 +162,7 @@ message Proposal {
(gogoproto.stdtime) = true
];
bytes signature = 7;
repeated TxKey tx_keys = 8;
repeated TxKey tx_keys = 8 [(wireguard.max_count) = 116508 /* types.MaxTxKeysPerProposal */];
EvidenceList evidence = 9;
Commit last_commit = 10;
Header header = 11 [(gogoproto.nullable) = false];
Expand Down
4 changes: 2 additions & 2 deletions sei-tendermint/proto/tendermint/types/types.wireguard.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions sei-tendermint/types/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ func TxHashFromProto(dp *tmproto.TxKey) (TxHash, error) {
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.

return TxHash{}, fmt.Errorf("invalid tx hash length: %d, expected: %d", len(dp.TxKey), sha256.Size)
}
var txBzs [sha256.Size]byte
copy(txBzs[:], dp.TxKey)

Expand Down
37 changes: 37 additions & 0 deletions sei-tendermint/types/mempool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package types

import (
"crypto/sha256"
"testing"

"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require"
tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types"
)

func TestTxHashFromProtoValidatesLength(t *testing.T) {
testCases := []struct {
name string
size int
}{
{name: "empty", size: 0},
{name: "short", size: sha256.Size - 1},
{name: "long", size: sha256.Size + 1},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := TxHashFromProto(&tmproto.TxKey{TxKey: make([]byte, tc.size)})
require.Error(t, err)
})
}

_, err := TxHashFromProto(nil)
require.Error(t, err)

key := make([]byte, sha256.Size)
for i := range key {
key[i] = byte(i)
}
hash, err := TxHashFromProto(&tmproto.TxKey{TxKey: key})
require.NoError(t, err)
require.Equal(t, TxHash(key), hash)
}
13 changes: 13 additions & 0 deletions sei-tendermint/types/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ var (
ErrInvalidBlockPartHash = errors.New("error invalid block part hash")
)

const (
// MaxConsensusMsgBytes is the largest message the consensus channels accept.
MaxConsensusMsgBytes = 4194304 // 4MB; NOTE: keep larger than types.PartSet sizes.

// txKeyEntryBytes is the encoded size of one populated Proposal.tx_keys
// entry: the field tag and length prefix of the repeated field, plus a TxKey
// holding one crypto.HashSize digest behind its own tag and length prefix.
txKeyEntryBytes = 1 + 1 + 1 + 1 + crypto.HashSize

// MaxTxKeysPerProposal is the largest number of tx keys a Proposal can carry
MaxTxKeysPerProposal = MaxConsensusMsgBytes / txKeyEntryBytes
)

// Proposal defines a block proposal for the consensus.
// It refers to the block by BlockID field.
// It must be signed by the correct proposer for the given Height/Round
Expand Down
37 changes: 36 additions & 1 deletion sei-tendermint/types/wireguard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import (
gogoproto "github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"

"github.com/sei-protocol/sei-chain/sei-tendermint/crypto"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/protoutils"
tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/types"
)

const maxCommitSignatures = types.MaxVotesCount
const (
maxCommitSignatures = types.MaxVotesCount
txKeySize = crypto.HashSize
)

func marshal(t *testing.T, m gogoproto.Message) []byte {
t.Helper()
Expand Down Expand Up @@ -54,6 +58,37 @@ func lcaeEvidence(n int) *tmproto.Evidence {
return &ev
}

func txKeyWith(n int) *tmproto.TxKey {
return &tmproto.TxKey{TxKey: make([]byte, n)}
}

func proposalSizeWithTxKeys(t *testing.T, n int) int {
t.Helper()
txKeys := make([]*tmproto.TxKey, n)
for i := range txKeys {
txKeys[i] = txKeyWith(txKeySize)
}
return len(marshal(t, &tmproto.Proposal{TxKeys: txKeys}))
}

// MaxTxKeysPerProposal must sit just above the largest tx key count the
// consensus channel can carry. The transport enforces that budget while
// reassembling a message, before the wireguard scan runs, so a cap at or above
// this point can only reject proposals that were never deliverable. The lower
// 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.

}

func TestSchemaForTxKey_AcceptsAtCap(t *testing.T) {
require.NoError(t, protoutils.Scan[*tmproto.TxKey](marshal(t, txKeyWith(txKeySize))))
}

func TestSchemaForTxKey_RejectsOverCap(t *testing.T) {
require.Error(t, protoutils.Scan[*tmproto.TxKey](marshal(t, txKeyWith(txKeySize+1))))
}

func TestSchemaForBlock_AcceptsLastCommitAtCap(t *testing.T) {
require.NoError(t, protoutils.Scan[*tmproto.Block](marshal(t,
consensusAssembledBlock(commitWith(maxCommitSignatures)))))
Expand Down
Loading