From c08d4a7fd036c6b81bccbec0406fc6c4e09d4d2e Mon Sep 17 00:00:00 2001 From: Matthias Fasching <5011972+fasmat@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:35:46 +0000 Subject: [PATCH] Update size of Ballot (#5619) ## Motivation This updates max scale collection sizes to support a network with up to 2.2 mio unique (valid) identities. --- beacon/beacon.go | 3 +-- beacon/message.go | 2 ++ common/types/ballot.go | 14 +++++++++++--- common/types/ballot_scale.go | 8 ++++---- common/types/block.go | 17 +++++++++++++++-- common/types/block_scale.go | 4 ++-- fetch/wire_types.go | 25 ++++++++++++++++++++++--- fetch/wire_types_scale.go | 4 ++-- hare3/types.go | 14 +++++++++++++- hare3/types_scale.go | 4 ++-- p2p/server/server.go | 2 +- p2p/server/server_scale.go | 4 ++-- 12 files changed, 77 insertions(+), 24 deletions(-) diff --git a/beacon/beacon.go b/beacon/beacon.go index f05dde430d7..cfbc7029f19 100644 --- a/beacon/beacon.go +++ b/beacon/beacon.go @@ -1126,8 +1126,7 @@ func atxThresholdFraction(kappa int, q *big.Rat, numATXs int) *big.Float { // TODO(nkryuchkov): Consider having a generic function for probabilities. func atxThreshold(kappa int, q *big.Rat, numATXs int) *big.Int { const ( - sigLengthBytes = 80 - sigLengthBits = sigLengthBytes * 8 + sigLengthBits = types.VrfSignatureSize * 8 ) fraction := atxThresholdFraction(kappa, q, numATXs) diff --git a/beacon/message.go b/beacon/message.go index f5534d1aa52..48d3d5f57fd 100644 --- a/beacon/message.go +++ b/beacon/message.go @@ -52,8 +52,10 @@ func ProposalFromVrf(vrf types.VrfSignature) Proposal { type FirstVotingMessageBody struct { EpochID types.EpochID // number of proposals is expected to be under 100, 1000 is a safe upper bound + // This expectation holds true for any number of smeshers on the network ValidProposals []Proposal `scale:"max=1000"` // number of proposals is expected to be under 100, 1000 is a safe upper bound + // This expectation holds true for any number of smeshers on the network PotentiallyValidProposals []Proposal `scale:"max=1000"` } diff --git a/common/types/ballot.go b/common/types/ballot.go index bf491c7fae2..8da1567eb0d 100644 --- a/common/types/ballot.go +++ b/common/types/ballot.go @@ -58,11 +58,19 @@ type Ballot struct { // the proof of the smeshers eligibility to vote and propose block content in this epoch. // Eligibilities must be produced in the ascending order. // the proofs are vrf signatures and need not be included in the ballot's signature. - // according to protocol there are 50 per layer, the rest is safety margin - EligibilityProofs []VotingEligibility `scale:"max=500"` + // + // The number of eligibility proofs depends on the smeshers weight and the total weight of the network. + // For epoch 16 the largest smesher had 888 SUs and the total weight of the network was ~20.2 Mio SUs. + // This means that the largest smesher received 888 / 20,200,000 = 0.0044% of all eligibility slots for the epoch. + // There are 4032 layers in an epoch and 50 eligibility slots per layer, so the largest smesher received + // 0.0044% * 4032 * 50 = ~9 eligibility slots. + // + // Assuming the largest smesher won't control more than 10% of space in the network, we can assume that the + // highest number of eligibilities in a single ballot will be below 25000. (10% of 4032 * 50 = 20160) + EligibilityProofs []VotingEligibility `scale:"max=25000"` // from the smesher's view, the set of ATXs eligible to vote and propose block content in this epoch // only present in smesher's first ballot of the epoch - ActiveSet []ATXID `scale:"max=100000"` + ActiveSet []ATXID `scale:"max=2200000"` // the following fields are kept private and from being serialized ballotID BallotID diff --git a/common/types/ballot_scale.go b/common/types/ballot_scale.go index b45bda376f6..c21c995a57e 100644 --- a/common/types/ballot_scale.go +++ b/common/types/ballot_scale.go @@ -37,14 +37,14 @@ func (t *Ballot) EncodeScale(enc *scale.Encoder) (total int, err error) { total += n } { - n, err := scale.EncodeStructSliceWithLimit(enc, t.EligibilityProofs, 500) + n, err := scale.EncodeStructSliceWithLimit(enc, t.EligibilityProofs, 25000) if err != nil { return total, err } total += n } { - n, err := scale.EncodeStructSliceWithLimit(enc, t.ActiveSet, 100000) + n, err := scale.EncodeStructSliceWithLimit(enc, t.ActiveSet, 2200000) if err != nil { return total, err } @@ -83,7 +83,7 @@ func (t *Ballot) DecodeScale(dec *scale.Decoder) (total int, err error) { total += n } { - field, n, err := scale.DecodeStructSliceWithLimit[VotingEligibility](dec, 500) + field, n, err := scale.DecodeStructSliceWithLimit[VotingEligibility](dec, 25000) if err != nil { return total, err } @@ -91,7 +91,7 @@ func (t *Ballot) DecodeScale(dec *scale.Decoder) (total int, err error) { t.EligibilityProofs = field } { - field, n, err := scale.DecodeStructSliceWithLimit[ATXID](dec, 100000) + field, n, err := scale.DecodeStructSliceWithLimit[ATXID](dec, 2200000) if err != nil { return total, err } diff --git a/common/types/block.go b/common/types/block.go index 050c5a71bbe..c1c9867653f 100644 --- a/common/types/block.go +++ b/common/types/block.go @@ -71,8 +71,21 @@ func (b Block) Equal(other Block) bool { type InnerBlock struct { LayerIndex LayerID TickHeight uint64 - Rewards []AnyReward `scale:"max=500"` - TxIDs []TransactionID `scale:"max=100000"` + // Rewards are the rewards for the block. + // + // Worst case scenario is that a single smesher identity has > 99.97% of the total weight of the network. + // In this case they will get all 50 available slots in all 4032 layers of the epoch. + // Additionally every other identity on the network that successfully published an ATX will get 1 slot. + // + // If we expect 2.2 Mio ATXs that would be a total of 2.2 Mio + 50 * 4032 = 2,401,600 slots. + // Since these are randomly distributed across the epoch, we can expect an average of n * p = + // 2,401,600 / 4032 = 595.7 rewards in a block with a standard deviation of sqrt(n * p * (1 - p)) = + // sqrt(2,401,600 * 1/4032 * 4031/4032) = 24.4 + // + // This means that we can expect a maximum of 595.7 + 6*24.4 = 743 rewards per block with + // > 99.9997% probability. + Rewards []AnyReward `scale:"max=800"` + TxIDs []TransactionID `scale:"max=100000"` } // RatNum represents a rational number with the numerator and denominator. diff --git a/common/types/block_scale.go b/common/types/block_scale.go index 12e3d7c422c..9b99fe0e3c3 100644 --- a/common/types/block_scale.go +++ b/common/types/block_scale.go @@ -45,7 +45,7 @@ func (t *InnerBlock) EncodeScale(enc *scale.Encoder) (total int, err error) { total += n } { - n, err := scale.EncodeStructSliceWithLimit(enc, t.Rewards, 500) + n, err := scale.EncodeStructSliceWithLimit(enc, t.Rewards, 800) if err != nil { return total, err } @@ -79,7 +79,7 @@ func (t *InnerBlock) DecodeScale(dec *scale.Decoder) (total int, err error) { t.TickHeight = uint64(field) } { - field, n, err := scale.DecodeStructSliceWithLimit[AnyReward](dec, 500) + field, n, err := scale.DecodeStructSliceWithLimit[AnyReward](dec, 800) if err != nil { return total, err } diff --git a/fetch/wire_types.go b/fetch/wire_types.go index 541a7f5c3af..933571a3608 100644 --- a/fetch/wire_types.go +++ b/fetch/wire_types.go @@ -22,7 +22,7 @@ type RequestMessage struct { // ResponseMessage is sent to the node as a response. type ResponseMessage struct { Hash types.Hash32 - Data []byte `scale:"max=89128960"` // limit to 85 MiB + Data []byte `scale:"max=89128960"` // limit to 85 MiB - keep in line with Response.Data in `p2p/server/server.go` } // RequestBatch is a batch of requests and a hash of all requests as ID. @@ -107,13 +107,32 @@ type MaliciousIDs struct { type EpochData struct { // to be in line with `EpochActiveSet` in common/types/activation.go // and DefaultConfig in datastore/store.go - // also double-check the size of `ResponseMessage` above + // + // When changing this value also check + // - the size of `ResponseMessage` above + // - the fields `EligibilityProofs` and `ActiveSet` in the type `Ballot` in common/types/ballot.go + // - the size of `Rewards` in the type `InnerBlock` in common/types/block.go + // - the size of `Ballots` in the type `LayerData` below + // - the size of `Proposals` in the type `Value` in hare3/types.go AtxIDs []types.ATXID `scale:"max=2200000"` } // LayerData is the data response for a given layer ID. type LayerData struct { - Ballots []types.BallotID `scale:"max=500"` // expected are 50 proposals per layer + safety margin + // Ballots contains the ballots for the given layer. + // + // Worst case scenario is that a single smesher identity has > 99.97% of the total weight of the network. + // In this case they will get all 50 available slots in all 4032 layers of the epoch. + // Additionally every other identity on the network that successfully published an ATX will get 1 slot. + // + // If we expect 2.2 Mio ATXs that would be a total of 2.2 Mio + 50 * 4032 = 2,401,600 slots. + // Since these are randomly distributed across the epoch, we can expect an average of n * p = + // 2,401,600 / 4032 = 595.7 ballots in a layer with a standard deviation of sqrt(n * p * (1 - p)) = + // sqrt(2,401,600 * 1/4032 * 4031/4032) = 24.4 + // + // This means that we can expect a maximum of 595.7 + 6*24.4 = 743 ballots per layer with + // > 99.9997% probability. + Ballots []types.BallotID `scale:"max=800"` } type OpinionRequest struct { diff --git a/fetch/wire_types_scale.go b/fetch/wire_types_scale.go index 48386635b36..da410995df3 100644 --- a/fetch/wire_types_scale.go +++ b/fetch/wire_types_scale.go @@ -281,7 +281,7 @@ func (t *EpochData) DecodeScale(dec *scale.Decoder) (total int, err error) { func (t *LayerData) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeStructSliceWithLimit(enc, t.Ballots, 500) + n, err := scale.EncodeStructSliceWithLimit(enc, t.Ballots, 800) if err != nil { return total, err } @@ -292,7 +292,7 @@ func (t *LayerData) EncodeScale(enc *scale.Encoder) (total int, err error) { func (t *LayerData) DecodeScale(dec *scale.Decoder) (total int, err error) { { - field, n, err := scale.DecodeStructSliceWithLimit[types.BallotID](dec, 500) + field, n, err := scale.DecodeStructSliceWithLimit[types.BallotID](dec, 800) if err != nil { return total, err } diff --git a/hare3/types.go b/hare3/types.go index fb4bc191fd0..6edc1349b45 100644 --- a/hare3/types.go +++ b/hare3/types.go @@ -75,7 +75,19 @@ func (ir IterRound) Absolute() uint32 { type Value struct { // Proposals is set in messages for preround and propose rounds. - Proposals []types.ProposalID `scale:"max=500"` + // + // Worst case scenario is that a single smesher identity has > 99.97% of the total weight of the network. + // In this case they will get all 50 available slots in all 4032 layers of the epoch. + // Additionally every other identity on the network that successfully published an ATX will get 1 slot. + // + // If we expect 2.2 Mio ATXs that would be a total of 2.2 Mio + 50 * 4032 = 2,401,600 slots. + // Since these are randomly distributed across the epoch, we can expect an average of n * p = + // 2,401,600 / 4032 = 595.7 eligibilities in a layer with a standard deviation of sqrt(n * p * (1 - p)) = + // sqrt(2,401,600 * 1/4032 * 4031/4032) = 24.4 + // + // This means that we can expect a maximum of 595.7 + 6*24.4 = 743 eligibilities in a layer with + // > 99.9997% probability. + Proposals []types.ProposalID `scale:"max=800"` // Reference is set in messages for commit and notify rounds. Reference *types.Hash32 } diff --git a/hare3/types_scale.go b/hare3/types_scale.go index 476372eed6c..b708cd92a2b 100644 --- a/hare3/types_scale.go +++ b/hare3/types_scale.go @@ -48,7 +48,7 @@ func (t *IterRound) DecodeScale(dec *scale.Decoder) (total int, err error) { func (t *Value) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeStructSliceWithLimit(enc, t.Proposals, 500) + n, err := scale.EncodeStructSliceWithLimit(enc, t.Proposals, 800) if err != nil { return total, err } @@ -66,7 +66,7 @@ func (t *Value) EncodeScale(enc *scale.Encoder) (total int, err error) { func (t *Value) DecodeScale(dec *scale.Decoder) (total int, err error) { { - field, n, err := scale.DecodeStructSliceWithLimit[types.ProposalID](dec, 500) + field, n, err := scale.DecodeStructSliceWithLimit[types.ProposalID](dec, 800) if err != nil { return total, err } diff --git a/p2p/server/server.go b/p2p/server/server.go index 56ec7e34147..a79385b50df 100644 --- a/p2p/server/server.go +++ b/p2p/server/server.go @@ -109,7 +109,7 @@ type Handler func(context.Context, []byte) ([]byte, error) // Response is a server response. type Response struct { - Data []byte `scale:"max=62914560"` // 60 MiB + Data []byte `scale:"max=89128960"` // 85 MiB Error string `scale:"max=1024"` // TODO(mafa): make error code instead of string } diff --git a/p2p/server/server_scale.go b/p2p/server/server_scale.go index e71f78f05eb..84ba13aa550 100644 --- a/p2p/server/server_scale.go +++ b/p2p/server/server_scale.go @@ -9,7 +9,7 @@ import ( func (t *Response) EncodeScale(enc *scale.Encoder) (total int, err error) { { - n, err := scale.EncodeByteSliceWithLimit(enc, t.Data, 62914560) + n, err := scale.EncodeByteSliceWithLimit(enc, t.Data, 89128960) if err != nil { return total, err } @@ -27,7 +27,7 @@ func (t *Response) EncodeScale(enc *scale.Encoder) (total int, err error) { func (t *Response) DecodeScale(dec *scale.Decoder) (total int, err error) { { - field, n, err := scale.DecodeByteSliceWithLimit(dec, 62914560) + field, n, err := scale.DecodeByteSliceWithLimit(dec, 89128960) if err != nil { return total, err }