Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

network: Limit message length based on Tag #5388

Merged
merged 64 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
a9131bb
WIP pre-rangexpr
iansuvak May 1, 2023
6ce8cea
rename to static method
iansuvak May 9, 2023
94dbf71
using 'static' methods
iansuvak May 10, 2023
0d17dc6
Added almost all bounds, missing pp, ue, ts
iansuvak May 12, 2023
73be798
UE = 67
iansuvak May 12, 2023
70ee7e3
Add totalallocbound for the infiniteloop
iansuvak May 15, 2023
8153660
add comments to exported constants and function
iansuvak May 15, 2023
67d89db
remove stateproofs from innertxns in proposal payload
iansuvak May 15, 2023
a181944
Fix some tests, add checking to limitedSlurper
iansuvak May 15, 2023
5ea2771
Add test ensuring non-zero MaxSize is defined for each Tag
iansuvak May 15, 2023
03df5fe
Cleanup
iansuvak May 15, 2023
0646f7f
Add missing comment
iansuvak May 15, 2023
50a267d
more missing comments and cleanup
iansuvak May 15, 2023
d6e6366
updated msgp + uint64 casting
iansuvak May 16, 2023
ecda744
Fix Stateproof and PP issues
iansuvak May 17, 2023
f7b369e
Add new AST test and more comments throughout
iansuvak May 17, 2023
4c25f21
Merge remote-tracking branch 'upstream/master' into ian/max-message-size
iansuvak May 31, 2023
64288a5
Add special handling for TS, UE, VB
iansuvak May 31, 2023
66cbf61
Reference the reason for UE having separate testing method
iansuvak Jun 1, 2023
f82c316
Remove unnecessary SignedTxnWithADMaxSize because it was used for pro…
iansuvak Jun 7, 2023
4580863
Update protocol/tags.go
iansuvak Jun 7, 2023
45b0d65
Changes after applying msgp alises fixes
algorandskiy Jun 7, 2023
e9ba543
Merge pull request #3 from algorandskiy/ian/max-message-size
iansuvak Jun 8, 2023
dbf99c2
Remove RoundMaxSize and AddressMaxSize
algorandskiy Jun 8, 2023
f6b4101
Merge remote-tracking branch 'ian/ian/max-message-size' into ian/max-…
algorandskiy Jun 8, 2023
895d9a7
add switch vs map benchmark
algorandskiy Jun 8, 2023
8d37cbb
use non-released msgp version
algorandskiy Jun 8, 2023
3c11c66
Merge remote-tracking branch 'upstream/master' into ian/max-message-size
algorandskiy Jun 8, 2023
ffb02ba
make tidy
algorandskiy Jun 8, 2023
fb8c934
fix reviewdog issues
iansuvak Jun 8, 2023
49e0e5d
remove unused method EvalDeltaNoInnersMaxSize
iansuvak Jun 8, 2023
bfff077
some test fixes; more to come
iansuvak Jun 9, 2023
82efc8c
Fix MaxSize constants due to bump in max GenesisID length
iansuvak Jun 9, 2023
5e3a434
Fix two more tests
iansuvak Jun 9, 2023
a3df2d2
Bump maxConsensusVersionLen for integration tests
iansuvak Jun 9, 2023
3289d45
fix TestWebsocketNetworkMessageOfInterest
iansuvak Jun 12, 2023
5d923e2
Merge remote-tracking branch 'upstream/master' into ian/max-message-size
iansuvak Jun 16, 2023
4925a33
re-run make msgp
iansuvak Jun 16, 2023
6c8b13d
%s/totalallocbound/maxallocbytes
iansuvak Jun 20, 2023
74ce8f1
%s/maxallocbytes/maxtotalbytes
iansuvak Jun 20, 2023
1e57b2b
Merge remote-tracking branch 'upstream/master' into ian/max-message-size
iansuvak Jun 20, 2023
c074625
fix merge issues
iansuvak Jun 20, 2023
a1b01cc
Fix remaining test
iansuvak Jun 20, 2023
5867661
add comments
iansuvak Jun 20, 2023
0c6d583
Merge remote-tracking branch 'upstream/master' into ian/max-message-size
iansuvak Jun 20, 2023
f610a9b
Add type alias backwards compat test
iansuvak Jun 20, 2023
cb3029b
make sure that the fake UE request fits into the block limit
iansuvak Jun 20, 2023
1f008bb
remove compat test
iansuvak Jun 21, 2023
a500239
more comments
iansuvak Jun 21, 2023
4bd07f5
Unexport MakeBlockRequestTopics
iansuvak Jun 21, 2023
e52b870
Add comment for alias-base type compat test
iansuvak Jun 21, 2023
03f4b01
Revert "Add comment for alias-base type compat test"
iansuvak Jun 21, 2023
e563c69
Update crypto/merklearray/proof.go
iansuvak Jun 21, 2023
02364e1
Remove race from TestDiscardUnrequestedBlockResponse
iansuvak Jun 21, 2023
289b062
gofmt shenanigans
iansuvak Jun 22, 2023
e0cc3ee
bump msgp version
iansuvak Jun 22, 2023
3e84468
Merge remote-tracking branch 'upstream/master' into ian/max-message-size
iansuvak Jun 22, 2023
5de753b
run go mod tidy in blockgenerator folder
iansuvak Jun 22, 2023
9db27dd
make sanity
iansuvak Jun 22, 2023
7d31ac7
move msgp directive outside of the doc-comment region
iansuvak Jun 23, 2023
b64ca5b
Update protocol/tags_test.go
iansuvak Jun 23, 2023
ffcf43e
%s/returnNames/namesOnly
iansuvak Jun 23, 2023
52cc1d8
add limitedReadSlurper test
iansuvak Jun 23, 2023
036f012
add a scary comment
iansuvak Jun 23, 2023
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
771 changes: 642 additions & 129 deletions agreement/msgp_gen.go

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions agreement/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ func (a SortPeriod) Less(i, j int) bool { return a[i] < a[j] }
func (a SortPeriod) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// SortRound defines SortInterface used by msgp to consistently sort maps with this type as key.
// note, for type aliases the base type is used for the interface
//msgp:ignore SortRound
//msgp:sort round SortRound
type SortRound []round
//msgp:sort basics.Round SortRound
type SortRound []basics.Round

func (a SortRound) Len() int { return len(a) }
func (a SortRound) Less(i, j int) bool { return a[i] < a[j] }
Expand Down
15 changes: 11 additions & 4 deletions catchup/universalFetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,24 @@ func (w *wsFetcherClient) address() string {
return fmt.Sprintf("[ws] (%s)", w.target.GetAddress())
}

// requestBlock send a request for block <round> and wait until it receives a response or a context expires.
func (w *wsFetcherClient) requestBlock(ctx context.Context, round basics.Round) ([]byte, error) {
// MakeBlockRequestTopics builds topics for requesting a block.
// It is exported for testing purposes since full request requires an additional nonce topic
// coming from the network implementation
func MakeBlockRequestTopics(r basics.Round) network.Topics {
iansuvak marked this conversation as resolved.
Show resolved Hide resolved
roundBin := make([]byte, binary.MaxVarintLen64)
binary.PutUvarint(roundBin, uint64(round))
topics := network.Topics{
binary.PutUvarint(roundBin, uint64(r))
return network.Topics{
network.MakeTopic(rpcs.RequestDataTypeKey,
[]byte(rpcs.BlockAndCertValue)),
network.MakeTopic(
rpcs.RoundKey,
roundBin),
}
}

// requestBlock send a request for block <round> and wait until it receives a response or a context expires.
func (w *wsFetcherClient) requestBlock(ctx context.Context, round basics.Round) ([]byte, error) {
topics := MakeBlockRequestTopics(round)
resp, err := w.target.Request(ctx, protocol.UniEnsBlockReqTag, topics)
if err != nil {
return nil, makeErrWsFetcherRequestFailed(round, w.target.GetAddress(), err.Error())
Expand Down
16 changes: 16 additions & 0 deletions catchup/universalFetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"testing"
"time"
Expand Down Expand Up @@ -312,3 +313,18 @@ func TestErrorTypes(t *testing.T) {
err6 := errHTTPResponseContentType{contentTypeCount: 1, contentType: "UNDEFINED"}
require.Equal(t, "HTTPFetcher.getBlockBytes: invalid content type: UNDEFINED", err6.Error())
}

// Block Request topics request is a handrolled msgpack message with deterministic size. This test ensures that it matches the defined
// constant in protocol
func TestMaxBlockRequestSize(t *testing.T) {
partitiontest.PartitionTest(t)

round := rand.Uint64()
topics := MakeBlockRequestTopics(basics.Round(round))
nonce := rand.Uint64() - 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Small nit: we should probably start using crypto/rand more in test cases like this

Copy link
Contributor

Choose a reason for hiding this comment

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

This example is so tiny it doesn't matter

nonceTopic := network.MakeNonceTopic(nonce)
topics = append(topics, nonceTopic)
serializedMsg := topics.MarshallTopics()
require.Equal(t, uint64(len(serializedMsg)), protocol.UniEnsBlockReqTag.MaxMessageSize())
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved

}
8 changes: 8 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ const ConfigurableConsensusProtocolsFilename = "consensus.json"
// do not expose in normal config so it is not in code generated local_defaults.go
const defaultRelayGossipFanout = 8

// MaxGenesisIDLen is the maximum length of the genesis ID set for purpose of setting
// allocbounds on structs containing GenesisID and for purposes of calculating MaxSize functions
// on those types. Current value is larger than the existing network IDs and the ones used in testing
const MaxGenesisIDLen = 128
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would one need to change this parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should never be necessary. The reason why this is so large is as I mentioned in the comment that there are tests that depend on the test name being passed around as the genesisID


// MaxEvalDeltaTotalLogSize is the maximum size of the sum of all log sizes in a single eval delta.
const MaxEvalDeltaTotalLogSize = 1024

// LoadConfigFromDisk returns a Local config structure based on merging the defaults
// with settings loaded from the config file from the custom dir. If the custom file
// cannot be loaded, the default config is returned (with the error from loading the
Expand Down
44 changes: 44 additions & 0 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,39 @@ var MaxAvailableAppProgramLen int
// to be taken offline, that would be proposed to be taken offline.
var MaxProposedExpiredOnlineAccounts int

// MaxAppTotalArgLen is the maximum number of bytes across all arguments of an application
// max sum([len(arg) for arg in txn.ApplicationArgs])
var MaxAppTotalArgLen int

// MaxAssetNameBytes is the maximum asset name length in bytes
var MaxAssetNameBytes int

// MaxAssetUnitNameBytes is the maximum asset unit name length in bytes
var MaxAssetUnitNameBytes int

// MaxAssetURLBytes is the maximum asset URL length in bytes
var MaxAssetURLBytes int

// MaxAppBytesValueLen is the maximum length of a bytes value used in an application's global or
// local key/value store
var MaxAppBytesValueLen int

// MaxAppBytesKeyLen is the maximum length of a key used in an application's global or local
// key/value store
var MaxAppBytesKeyLen int

// StateProofTopVoters is a bound on how many online accounts get to
// participate in forming the state proof, by including the
// top StateProofTopVoters accounts (by normalized balance) into the
// vector commitment.
var StateProofTopVoters int

// MaxTxnBytesPerBlock determines the maximum number of bytes
// that transactions can take up in a block. Specifically,
// the sum of the lengths of encodings of each transaction
// in a block must not exceed MaxTxnBytesPerBlock.
var MaxTxnBytesPerBlock int

func checkSetMax(value int, curMax *int) {
if value > *curMax {
*curMax = value
Expand Down Expand Up @@ -627,6 +660,17 @@ func checkSetAllocBounds(p ConsensusParams) {
checkSetMax(p.MaxAppProgramLen, &MaxLogCalls)
checkSetMax(p.MaxInnerTransactions*p.MaxTxGroupSize, &MaxInnerTransactionsPerDelta)
checkSetMax(p.MaxProposedExpiredOnlineAccounts, &MaxProposedExpiredOnlineAccounts)

// These bounds are exported to make them available to the msgp generator for calculating
// maximum valid message size for each message going across the wire.
checkSetMax(p.MaxAppTotalArgLen, &MaxAppTotalArgLen)
checkSetMax(p.MaxAssetNameBytes, &MaxAssetNameBytes)
checkSetMax(p.MaxAssetUnitNameBytes, &MaxAssetUnitNameBytes)
checkSetMax(p.MaxAssetURLBytes, &MaxAssetURLBytes)
checkSetMax(p.MaxAppBytesValueLen, &MaxAppBytesValueLen)
checkSetMax(p.MaxAppKeyLen, &MaxAppBytesKeyLen)
checkSetMax(int(p.StateProofTopVoters), &StateProofTopVoters)
checkSetMax(p.MaxTxnBytesPerBlock, &MaxTxnBytesPerBlock)
}

// SaveConfigurableConsensus saves the configurable protocols file to the provided data directory.
Expand Down
28 changes: 28 additions & 0 deletions crypto/merklearray/msgp_gen.go

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

18 changes: 18 additions & 0 deletions crypto/merklearray/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"

"github.com/algorand/go-algorand/crypto"
"github.com/algorand/msgp/msgp"
)

// Proof is used to convince a verifier about membership of leaves: h0,h1...hn
Expand All @@ -41,12 +42,29 @@ type Proof struct {
// SingleLeafProof is used to convince a verifier about membership of a specific
// leaf h at index i on a tree. The verifier has a trusted value of the tree
// root hash. it corresponds to merkle verification path.
//msgp:maxsize ignore SingleLeafProof
type SingleLeafProof struct {
_struct struct{} `codec:",omitempty,omitemptyarray"`

Proof
}

// ProofMaxSizeByElements returns the maximum msgp encoded size of merklearray.Proof structs containing n signatures
// This is necessary because the allocbounds on the proof are actual theoretical bounds but for calculating maximum
// proof size for individual message types we have smaller valid bounds.
func ProofMaxSizeByElements(n int) (s int) {
s = 1 + 4
Copy link
Contributor

Choose a reason for hiding this comment

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

I am very worried about having such fine-grained calculations done manually.
The tradeoff we need to consider is:
what is the risk of allowing loose bounds versus having such fine-grained calculations done manually.

Still considering this, and making sure the calculations are correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's actually not fully manual. It's a modification of the autogenerated test: here https://github.com/algorand/go-algorand/pull/5388/files#diff-add6a5ef46c2629eb7484bf3113df670d70cfdb6661ca69e236a778a7e1cb4c4R334

and we have a test here https://github.com/algorand/go-algorand/pull/5388/files#diff-181a1af1a38a976d2ddce9a94f778520e2ad787079c3cc99dbd1b7483386aa73R248 that ensures that that auto-generated ProofMaxSize and and ProofMaxSizeByElements output the same value when the allocbound is used as the input

Copy link
Contributor

Choose a reason for hiding this comment

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

The test is very good and reliable. However, the mapping of the function SingleLeafProofMaxSize to SingleLeafProof is not obvious for someone who is not aware of this arrangement.

In this particular case, this function is not needed. It can be implemented by setting a constant bound directly to the Proof field in:

        Signature struct {
                _struct struct{} `codec:",omitempty,omitemptyarray"`

                Signature             crypto.FalconSignature      `codec:"sig"`
                VectorCommitmentIndex uint64                      `codec:"idx"`
                Proof                 merklearray.SingleLeafProof `codec:"prf"`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah but we do have a pattern for doing this manual implementation already for basics.MicroAlgos and this solution will continue working if we ever use SingleLeafProof elsewhere. Finding it's maxtotalbytes bound and using it is more implicit IMO.

// Calculating size of slice: z.Path
s += msgp.ArrayHeaderSize + n*(crypto.GenericDigestMaxSize())
s += 4 + crypto.HashFactoryMaxSize() + 3 + msgp.Uint8Size
return
}

// SingleLeafProofMaxSize returns the maximum msgp encoded size of proof verifying a single leaf
func SingleLeafProofMaxSize() int {
return ProofMaxSizeByElements(MaxEncodedTreeDepth)
Copy link
Contributor

Choose a reason for hiding this comment

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

Everything here is calculated from constants. Can't SingleLeafProofMaxSize just return a constant pre-calculated value?
Then, a test can be added to confirm the constant value remains valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can make the change but none of these MaxSize() functions are used at runtime outside of testing. The only thing that this PR uses at runtime is constants populating the MaxSize() method on the protocol.Tag type.

Copy link
Contributor

Choose a reason for hiding this comment

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

none of these MaxSize() functions are used at runtime outside of testing.

Then these functions should be defined in the test files, not in production files.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm open to that but they are theoretically valid in production as well and on msgp library side they are generated like all the other functions. Moving the core generation pass to only generate code in the test path would break the symmetry

}

// GetFixedLengthHashableRepresentation serializes the proof into a sequence of bytes.
// it basically concatenates the elements of the verification path one after another.
// The function returns a fixed length array for each hash function. which is 1 + MaxEncodedTreeDepth * digestsize
Expand Down
9 changes: 9 additions & 0 deletions crypto/merklearray/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,12 @@ func recomputePath(p []byte) []crypto.GenericDigest {
}
return computedPath
}

func TestMaxSizeCalculation(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

// Ensure that the manually generated ProofMaxSizeByElements matches the autogenerated ProofMaxSize() function
// If this breaks either the allocbound has changed or the Proof struct definition has changed
require.Equal(t, ProofMaxSizeByElements(MaxNumLeavesOnEncodedTree/2), ProofMaxSize())
}
48 changes: 48 additions & 0 deletions crypto/merklesignature/msgp_gen.go

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

Loading