use crate::{
BoundedBlock,
marshal::{core::Variant, resolver::handler::Key},
simplex::types::{Finalization, Notarization},
};
use commonware_codec::Read;
use commonware_cryptography::certificate::Scheme;
use commonware_resolver::p2p::max_message_size;
fn max_backfill_size<V: Variant, S: Scheme>(participants: u32, application: usize) -> usize {
let block = V::max_backfill_block_size(application);
let notarized = Notarization::<S, V::Commitment>::max_encode_size(participants) + block;
let finalized = Finalization::<S, V::Commitment>::max_encode_size(participants) + application;
max_message_size::<Key<V::Commitment>>(block.max(notarized).max(finalized))
}
pub fn max_payload_size<V: Variant, S: Scheme>(
participants: u32,
block_codec_config: &<V::ApplicationBlock as Read>::Cfg,
) -> usize
where
V::ApplicationBlock: BoundedBlock,
{
let application = V::ApplicationBlock::max_encode_size(block_codec_config);
max_backfill_size::<V, S>(participants, application)
.max(V::max_buffer_payload_size(participants, application))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
marshal::{
coding::{
Coding,
types::{Shard, coding_config_for_participants},
},
mocks::{
block::encode_block,
harness::{CodingB, CodingCtx, default_leader, make_coding_block},
},
standard::Standard,
},
simplex::{
scheme::{Scheme, bls12381_multisig, bls12381_threshold, ed25519, secp256r1},
types::{Finalization, Notarization, Proposal, Subject},
},
types::{Epoch, Height, Round, View, coding::Commitment},
};
use commonware_codec::{Decode, Encode, EncodeSize, FixedSize, MaxEncodeSize};
use commonware_coding::{Config as CodingConfig, ReedSolomon, Scheme as _};
use commonware_cryptography::{
Committable, Sha256 as Sha256Hasher,
bls12381::primitives::variant::{MinPk, MinSig},
certificate::{Scoped, Verifier as _, mocks::Fixture},
ed25519::PublicKey,
sha256::Digest as Sha256,
};
use commonware_math::algebra::Random as _;
use commonware_parallel::Sequential;
use commonware_resolver::p2p::{MAX_RESPONSE_OVERHEAD, REQUEST_OVERHEAD};
use commonware_utils::{Widen, non_empty, ordered::Quorum, test_rng};
use std::sync::Arc;
const NAMESPACE: &[u8] = b"_COMMONWARE_CONSENSUS_MARSHAL_LIMITS";
type RS = ReedSolomon<Sha256Hasher>;
type StandardVariant = Standard<CodingB>;
type CodingVariant = Coding<CodingB, RS, Sha256Hasher, PublicKey>;
fn check_encoding<V: Variant, S: Scheme<V::Commitment>>(
fixture: Fixture<S>,
application: usize,
) {
let schemes = fixture.schemes;
let participants = u32::try_from(schemes.len()).unwrap();
let max_certificate_size = S::max_certificate_size(participants);
let scoped = Scoped::verifier(Arc::new(fixture.verifier));
let backfill = max_backfill_size::<V, S>(participants, application);
let block = V::max_backfill_block_size(application);
assert_eq!(
backfill,
Proposal::<V::Commitment>::MAX_ENCODE_SIZE
+ max_certificate_size
+ block
+ MAX_RESPONSE_OVERHEAD
);
let commitment = V::Commitment::random(test_rng());
for key in [
Key::Block(commitment),
Key::Finalized {
height: Height::new(u64::MAX),
},
Key::Notarized {
round: Round::new(Epoch::new(u64::MAX), View::new(u64::MAX)),
},
] {
assert!(key.encode().len() + REQUEST_OVERHEAD <= backfill);
}
assert!(block + MAX_RESPONSE_OVERHEAD <= backfill);
for view in [1, 127, 128, u64::MAX] {
let proposal = Proposal::new(
Round::new(Epoch::new(view), View::new(view)),
View::new(view - 1),
V::Commitment::random(test_rng()),
);
for count in [
Widen::widen(schemes[0].participants().quorum::<S::Faults>()),
schemes.len(),
] {
let attestations = schemes.iter().take(count).map(|scheme| {
scheme
.sign::<V::Commitment>(Subject::Notarize {
proposal: &proposal,
})
.unwrap()
});
let certificate = schemes[0]
.assemble(non_empty![@attestations], &Sequential)
.unwrap();
let encoded = certificate.encode();
assert!(encoded.len() <= max_certificate_size);
assert!(
S::Certificate::decode_cfg(encoded, &scoped.certificate_codec_config()).is_ok()
);
let notarization = Notarization::<S, V::Commitment> {
proposal: proposal.clone(),
certificate: certificate.clone(),
};
let finalization = Finalization::<S, V::Commitment> {
proposal: proposal.clone(),
certificate,
};
let notarized = notarization.encode().len() + block + MAX_RESPONSE_OVERHEAD;
let finalized = finalization.encode().len() + application + MAX_RESPONSE_OVERHEAD;
assert!(notarized <= backfill);
assert!(finalized <= backfill);
}
}
}
#[test]
fn fixed_certificate_overhead() {
let mut rng = test_rng();
check_encoding::<StandardVariant, _>(
bls12381_threshold::standard::fixture::<MinSig, _>(&mut rng, NAMESPACE, 4),
0,
);
check_encoding::<StandardVariant, _>(
bls12381_threshold::standard::fixture::<MinPk, _>(&mut rng, NAMESPACE, 4),
1000,
);
check_encoding::<StandardVariant, _>(
bls12381_threshold::vrf::fixture::<MinSig, _>(&mut rng, NAMESPACE, 4),
0,
);
check_encoding::<StandardVariant, _>(
bls12381_threshold::vrf::fixture::<MinPk, _>(&mut rng, NAMESPACE, 4),
1000,
);
}
#[test]
fn variable_certificate_overhead() {
let mut rng = test_rng();
for participants in [4, 128] {
check_encoding::<StandardVariant, _>(
ed25519::fixture(&mut rng, NAMESPACE, participants),
1000,
);
}
check_encoding::<StandardVariant, _>(secp256r1::fixture(&mut rng, NAMESPACE, 4), 0);
check_encoding::<StandardVariant, _>(
bls12381_multisig::fixture::<MinSig, _>(&mut rng, NAMESPACE, 4),
0,
);
check_encoding::<StandardVariant, _>(
bls12381_multisig::fixture::<MinPk, _>(&mut rng, NAMESPACE, 4),
1000,
);
}
#[test]
fn coding_commitment_overhead() {
let mut rng = test_rng();
check_encoding::<CodingVariant, _>(
bls12381_threshold::vrf::fixture::<MinSig, _>(&mut rng, NAMESPACE, 4),
1000,
);
for participants in [4, 128] {
check_encoding::<CodingVariant, _>(
ed25519::fixture(&mut rng, NAMESPACE, participants),
1000,
);
}
}
#[test]
fn standard_limits() {
let backfill = max_backfill_size::<StandardVariant, ed25519::Scheme>(4, 1000);
let empty = max_backfill_size::<StandardVariant, ed25519::Scheme>(4, 0);
assert_eq!(backfill, empty + 1000);
assert_eq!(StandardVariant::max_buffer_payload_size(4, 1000), 1000);
let application = CodingB::max_encode_size(&());
assert_eq!(
max_payload_size::<StandardVariant, ed25519::Scheme>(4, &()),
max_backfill_size::<StandardVariant, ed25519::Scheme>(4, application)
);
}
#[test]
fn largest_block() {
let empty = max_backfill_size::<StandardVariant, ed25519::Scheme>(4, 0);
let widest: usize = Widen::widen(u32::MAX);
let largest = widest - (empty - MAX_RESPONSE_OVERHEAD);
let backfill = max_backfill_size::<StandardVariant, ed25519::Scheme>(4, largest);
assert_eq!(backfill, widest + MAX_RESPONSE_OVERHEAD);
}
#[test]
#[should_panic(expected = "value must fit the u32 length prefix")]
fn response_overflow() {
let empty = max_backfill_size::<StandardVariant, ed25519::Scheme>(4, 0);
let widest: usize = Widen::widen(u32::MAX);
let largest = widest - (empty - MAX_RESPONSE_OVERHEAD);
max_backfill_size::<StandardVariant, ed25519::Scheme>(4, largest + 1);
}
#[test]
#[should_panic(expected = "committee must have at least 4 members")]
fn coding_too_few_participants() {
CodingVariant::max_buffer_payload_size(3, 100);
}
#[test]
#[should_panic(expected = "backend must support the shard counts")]
fn coding_unsupported_participants() {
CodingVariant::max_buffer_payload_size(49_154, 100);
}
#[test]
fn coding_supported_participants() {
assert!(CodingVariant::max_buffer_payload_size(49_153, 100) > 0);
}
fn widest_shard(participants: u16, data: usize) -> (usize, u16) {
let data = vec![0xAB; data];
let mut widest = (0, 0);
for n in 4..=participants {
let config = coding_config_for_participants(n);
let (root, shards) = RS::encode(&config, data.as_slice(), &Sequential).unwrap();
let commitment = Commitment::<CodingB, RS, Sha256Hasher>::from((
Sha256::random(test_rng()),
root,
Sha256::random(test_rng()),
config,
));
for (index, shard) in shards.into_iter().enumerate() {
let index = u16::try_from(index).unwrap();
let size = Shard::new(commitment, index, shard).encode().len();
if size > widest.0 {
widest = (size, n);
}
}
}
widest
}
#[test]
fn coded_block_shards_match_encoded_data() {
let parent = Commitment::<CodingB, RS, Sha256Hasher>::from((
Sha256::random(test_rng()),
Sha256::random(test_rng()),
Sha256::random(test_rng()),
coding_config_for_participants(4),
));
let context = CodingCtx {
round: Round::new(Epoch::zero(), View::new(1)),
leader: default_leader(),
parent: (View::zero(), parent),
};
let block = make_coding_block(context, Sha256::random(test_rng()), Height::new(1), 7);
for n in [4u16, 5, 33] {
let config = coding_config_for_participants(n);
let coded = encode_block::<CodingB, RS, Sha256Hasher>(block.clone(), config);
let data = coded.encode();
assert_eq!(
data.len(),
Coding::<CodingB, RS, Sha256Hasher, PublicKey>::max_backfill_block_size(
block.encode_size()
)
);
let (root, shards) = RS::encode(&config, data.as_ref(), &Sequential).unwrap();
assert_eq!(root, coded.commitment().root());
for (index, shard) in shards.into_iter().enumerate() {
let index = u16::try_from(index).unwrap();
let real = coded.shard(index).expect("shard must be encoded");
assert_eq!(
real.encode().len(),
Shard::new(coded.commitment(), index, shard).encode().len()
);
}
}
}
#[test]
fn coding_limits() {
for (participants, block, n, expected) in [(16, 64 * 1024, 5, 32986), (33, 100, 33, 320)] {
let buffer = CodingVariant::max_buffer_payload_size(u32::from(participants), block);
let widest = widest_shard(participants, block + CodingConfig::SIZE);
assert!(widest.0 <= buffer);
assert_eq!(buffer, expected);
assert_eq!(widest.1, n);
}
}
#[test]
fn coding_covers_shards() {
type S = bls12381_threshold::standard::Scheme<PublicKey, MinSig>;
for (participants, block, expected) in [(33, 100, 320), (100, 100, 348)] {
let n = u32::from(participants);
let (widest, _) = widest_shard(participants, block + CodingConfig::SIZE);
assert!(widest > max_backfill_size::<CodingVariant, S>(n, block));
let buffer = CodingVariant::max_buffer_payload_size(n, block);
assert!(widest <= buffer);
assert_eq!(buffer, expected);
}
let application = CodingB::max_encode_size(&());
for n in [4, 33, 100] {
assert_eq!(
max_payload_size::<CodingVariant, S>(n, &()),
max_backfill_size::<CodingVariant, S>(n, application)
.max(CodingVariant::max_buffer_payload_size(n, application))
);
}
}
}