Skip to content

Commit

Permalink
network: Limit message length based on Tag (algorand#5388)
Browse files Browse the repository at this point in the history
This PR implements specific message length limits based on the protocol Tag
of the message being currently read in the wsPeer.
The new <TypeName>MaxSize() int methods are being generated by the msgp library
based on the supplied allocbounds and the new struct annotation totalallocbound
used to limit the total number of bytes encoded in a collection type.

New constants based on <TypeName>MaxSize() values  are defined
and Tag.MaxSize() method switching between them is used
when resetting the LimitedReaderSlurper in wsPeer's readLoop.
  • Loading branch information
iansuvak authored Jun 23, 2023
1 parent f56b9bd commit c223dbc
Show file tree
Hide file tree
Showing 63 changed files with 3,430 additions and 532 deletions.
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 @@ -54,10 +54,11 @@ 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
13 changes: 9 additions & 4 deletions catchup/universalFetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,22 @@ 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.
func makeBlockRequestTopics(r basics.Round) network.Topics {
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
nonceTopic := network.MakeNonceTopic(nonce)
topics = append(topics, nonceTopic)
serializedMsg := topics.MarshallTopics()
require.Equal(t, uint64(len(serializedMsg)), protocol.UniEnsBlockReqTag.MaxMessageSize())

}
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

// 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.

49 changes: 49 additions & 0 deletions crypto/merklearray/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,38 @@ 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
// at indexes i0,i1...in on a tree. The verifier has a trusted value of the tree
// root hash.
// Path is bounded by MaxNumLeaves since there could be multiple reveals, and
// given the distribution of the elt positions and the depth of the tree,
// the path length can increase up to 2^MaxTreeDepth / 2
//
// . Consider two different reveals for the same tree:
// .
// . z5
// . z3 z4
// . y z z1 z2
// . q r s t u v w x
// . a b c d e f g h i j k l m n o p
// . ^
// . hints: [a, r, z, z4]
// . len(hints) = 4
// . You need a to combine with b to get q, need r to combine with the computed q and get y, and so on.
// . The worst case is this:
// .
// . z5
// . z3 z4
// . y z z1 z2
// . q r s t u v w x
// . a b c d e f g h i j k l m n o p
// . ^ ^ ^ ^ ^ ^ ^ ^
// .
// . hints: [b, d, e, g, j, l, m, o]
// . len(hints) = 2^4/2
type Proof struct {
_struct struct{} `codec:",omitempty,omitemptyarray"`

Expand All @@ -41,12 +68,34 @@ 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.
// The msgp directive belows tells msgp to not generate SingleLeafProofMaxSize method since we have one manually defined in this file.
//
//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.
// Exported because it's used in the stateproof package for ensuring that SigPartProof constant is correct size.
func ProofMaxSizeByElements(n int) (s int) {
s = 1 + 4
// 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
// It is manually defined here instead of letting msgp do it since we cannot annotate the embedded Proof struct
// with maxtotalbytes for msgp to autogenerate it.
func SingleLeafProofMaxSize() int {
return ProofMaxSizeByElements(MaxEncodedTreeDepth)
}

// 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())
}
Loading

0 comments on commit c223dbc

Please sign in to comment.