Skip to content

Commit

Permalink
Merge pull request #2568 from subspace/add-runtime-api-for-bundle-weight
Browse files Browse the repository at this point in the history
Implement bundle limit runtime api to limit bundle weight and size
  • Loading branch information
NingLin-P authored Mar 11, 2024
2 parents 58aa974 + 3c11d8e commit ea3a4fd
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 23 deletions.
59 changes: 56 additions & 3 deletions crates/pallet-domains/src/domain_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_core::Get;
use sp_domains::{
derive_domain_block_hash, DomainId, DomainsDigestItem, DomainsTransfersTracker,
OperatorAllowList, RuntimeId, RuntimeType,
derive_domain_block_hash, DomainBundleLimit, DomainId, DomainsDigestItem,
DomainsTransfersTracker, OperatorAllowList, RuntimeId, RuntimeType,
};
use sp_runtime::traits::{CheckedAdd, Zero};
use sp_runtime::traits::{CheckedAdd, IntegerSquareRoot, Zero};
use sp_runtime::DigestItem;
use sp_std::collections::btree_map::BTreeMap;
use sp_std::collections::btree_set::BTreeSet;
Expand All @@ -55,6 +55,7 @@ pub enum Error {
MaxInitialDomainAccounts,
DuplicateInitialAccounts,
FailedToGenerateRawGenesis(crate::runtime_registry::Error),
BundleLimitCalculationOverflow,
}

#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -121,6 +122,16 @@ where

Ok(())
}

pub(crate) fn calculate_bundle_limit<T: Config>(&self) -> Result<DomainBundleLimit, Error> {
calculate_max_bundle_weight_and_size(
self.max_block_size,
self.max_block_weight,
T::ConsensusSlotProbability::get(),
self.bundle_slot_probability,
)
.ok_or(Error::BundleLimitCalculationOverflow)
}
}

#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -170,6 +181,9 @@ pub(crate) fn can_instantiate_domain<T: Config>(
Error::InvalidSlotProbability
);

// Ensure the bundle limit can be calculated successfully
let _ = domain_config.calculate_bundle_limit::<T>()?;

ensure!(
T::Currency::reducible_balance(owner_account_id, Preservation::Protect, Fortitude::Polite)
>= T::DomainInstantiationDeposit::get(),
Expand Down Expand Up @@ -301,6 +315,45 @@ pub(crate) fn do_update_domain_allow_list<T: Config>(
})
}

// See https://forum.subspace.network/t/on-bundle-weight-limits-sum/2277 for more details
// about the formula
pub(crate) fn calculate_max_bundle_weight_and_size(
max_domain_block_size: u32,
max_domain_block_weight: Weight,
consensus_slot_probability: (u64, u64),
bundle_slot_probability: (u64, u64),
) -> Option<DomainBundleLimit> {
// (n1 / d1) / (n2 / d2) is equal to (n1 * d2) / (d1 * n2)
// This represents: bundle_slot_probability/SLOT_PROBABILITY
let expected_bundles_per_block = bundle_slot_probability
.0
.checked_mul(consensus_slot_probability.1)?
.checked_div(
bundle_slot_probability
.1
.checked_mul(consensus_slot_probability.0)?,
)?;

// This represents: Ceil[2*Sqrt[bundle_slot_probability/SLOT_PROBABILITY]])
let std_of_expected_bundles_per_block = expected_bundles_per_block
.integer_sqrt()
.checked_mul(2)?
.checked_add(1)?;

// max_bundle_weight = TargetDomainBlockWeight/(bundle_slot_probability/SLOT_PROBABILITY+ Ceil[2*Sqrt[ bundle_slot_probability/SLOT_PROBABILITY]])
let max_bundle_weight = max_domain_block_weight
.checked_div(expected_bundles_per_block.checked_add(std_of_expected_bundles_per_block)?)?;

// max_bundle_size = TargetDomainBlockSize/(bundle_slot_probability/SLOT_PROBABILITY+ Ceil[2*Sqrt[ bundle_slot_probability/SLOT_PROBABILITY]])
let max_bundle_size = (max_domain_block_size as u64)
.checked_div(expected_bundles_per_block.checked_add(std_of_expected_bundles_per_block)?)?;

Some(DomainBundleLimit {
max_bundle_size: max_bundle_size as u32,
max_bundle_weight,
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
41 changes: 37 additions & 4 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub mod weights;
extern crate alloc;

use crate::block_tree::verify_execution_receipt;
use crate::domain_registry::Error as DomainRegistryError;
use crate::staking::OperatorStatus;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
Expand All @@ -55,8 +56,9 @@ use sp_consensus_subspace::WrappedPotOutput;
use sp_core::H256;
use sp_domains::bundle_producer_election::BundleProducerElectionParams;
use sp_domains::{
DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId,
OperatorPublicKey, RuntimeId, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, EMPTY_EXTRINSIC_ROOT,
DomainBlockLimit, DomainBundleLimit, DomainId, DomainInstanceData, ExecutionReceipt,
OpaqueBundle, OperatorId, OperatorPublicKey, RuntimeId,
DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, EMPTY_EXTRINSIC_ROOT,
};
use sp_domains_fraud_proof::fraud_proof::{
FraudProof, InvalidBlockFeesProof, InvalidDomainBlockHashProof,
Expand Down Expand Up @@ -257,6 +259,10 @@ mod pallet {
#[pallet::constant]
type BlockTreePruningDepth: Get<DomainBlockNumberFor<Self>>;

/// Consensus chain slot probability.
#[pallet::constant]
type ConsensusSlotProbability: Get<(u64, u64)>;

/// The maximum block size limit for all domain.
#[pallet::constant]
type MaxDomainBlockSize: Get<u32>;
Expand Down Expand Up @@ -626,6 +632,10 @@ mod pallet {
SlotInTheFuture,
/// The bundle is built on a slot in the past
SlotInThePast,
/// Unable to calculate bundle limit
UnableToCalculateBundleLimit,
/// Bundle weight exceeds the max bundle weight limit
BundleTooHeavy,
}

#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
Expand Down Expand Up @@ -1624,13 +1634,22 @@ impl<T: Config> Pallet<T> {
.ok_or(BundleError::InvalidDomainId)?
.domain_config;

// TODO: check bundle weight with `domain_config.max_block_weight`
let domain_bundle_limit = domain_config
.calculate_bundle_limit::<T>()
.map_err(|_| BundleError::UnableToCalculateBundleLimit)?;

ensure!(
opaque_bundle.size() <= domain_config.max_block_size,
opaque_bundle.size() <= domain_bundle_limit.max_bundle_size,
BundleError::BundleTooLarge
);

ensure!(
opaque_bundle
.estimated_weight()
.all_lte(domain_bundle_limit.max_bundle_weight),
BundleError::BundleTooHeavy
);

Self::check_extrinsics_root(opaque_bundle)?;

let proof_of_election = &sealed_header.header.proof_of_election;
Expand Down Expand Up @@ -2003,6 +2022,20 @@ impl<T: Config> Pallet<T> {
})
}

/// Returns the domain bundle limit of the given domain
pub fn domain_bundle_limit(
domain_id: DomainId,
) -> Result<Option<DomainBundleLimit>, DomainRegistryError> {
let domain_config = match DomainRegistry::<T>::get(domain_id) {
None => return Ok(None),
Some(domain_obj) => domain_obj.domain_config,
};

let bundle_limit = domain_config.calculate_bundle_limit::<T>()?;

Ok(Some(bundle_limit))
}

/// Returns if there are any ERs in the challenge period that have non empty extrinsics.
/// Note that Genesis ER is also considered special and hence non empty
pub fn non_empty_er_exists(domain_id: DomainId) -> bool {
Expand Down
138 changes: 137 additions & 1 deletion crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::block_tree::BlockTreeNode;
use crate::domain_registry::{DomainConfig, DomainObject};
use crate::domain_registry::{calculate_max_bundle_weight_and_size, DomainConfig, DomainObject};
use crate::staking::Operator;
use crate::{
self as pallet_domains, BalanceOf, BlockSlot, BlockTree, BlockTreeNodes, BundleError, Config,
Expand Down Expand Up @@ -114,6 +114,7 @@ parameter_types! {
pub const DomainInstantiationDeposit: Balance = 100;
pub const MaxDomainNameLength: u32 = 16;
pub const BlockTreePruningDepth: u32 = 16;
pub const SlotProbability: (u64, u64) = (1, 6);
}

pub struct ConfirmationDepthK;
Expand Down Expand Up @@ -309,6 +310,7 @@ impl pallet_domains::Config for Test {
type DomainsTransfersTracker = MockDomainsTransfersTracker;
type MaxInitialDomainAccounts = MaxInitialDomainAccounts;
type MinInitialDomainAccountBalance = MinInitialDomainAccountBalance;
type ConsensusSlotProbability = SlotProbability;
}

pub struct ExtrinsicStorageFees;
Expand Down Expand Up @@ -1364,3 +1366,137 @@ fn test_basic_fraud_proof_processing() {
});
}
}

#[test]
fn test_bundle_limit_calculation() {
let table = vec![
((1500, 1599), (1, 6), (1, 1), (136, 145)),
((1501, 1598), (2, 7), (2, 99), (1501, 1598)),
((1502, 1597), (3, 8), (3, 98), (1502, 1597)),
((1503, 1596), (4, 9), (4, 97), (1503, 1596)),
((1504, 1595), (5, 10), (5, 96), (1504, 1595)),
((1505, 1594), (6, 11), (6, 95), (1505, 1594)),
((1506, 1593), (7, 12), (7, 94), (1506, 1593)),
((1507, 1592), (8, 13), (8, 93), (1507, 1592)),
((1508, 1591), (9, 14), (9, 92), (1508, 1591)),
((1509, 1590), (10, 15), (10, 91), (1509, 1590)),
((1510, 1589), (11, 16), (11, 90), (1510, 1589)),
((1511, 1588), (12, 17), (12, 89), (1511, 1588)),
((1512, 1587), (13, 18), (13, 88), (1512, 1587)),
((1513, 1586), (14, 19), (14, 87), (1513, 1586)),
((1514, 1585), (15, 20), (15, 86), (1514, 1585)),
((1515, 1584), (16, 21), (16, 85), (1515, 1584)),
((1516, 1583), (17, 22), (17, 84), (1516, 1583)),
((1517, 1582), (18, 23), (18, 83), (1517, 1582)),
((1518, 1581), (19, 24), (19, 82), (1518, 1581)),
((1519, 1580), (20, 25), (20, 81), (1519, 1580)),
((1520, 1579), (21, 26), (21, 80), (1520, 1579)),
((1521, 1578), (22, 27), (22, 79), (1521, 1578)),
((1522, 1577), (23, 28), (23, 78), (1522, 1577)),
((1523, 1576), (24, 29), (24, 77), (1523, 1576)),
((1524, 1575), (25, 30), (25, 76), (1524, 1575)),
((1525, 1574), (26, 31), (26, 75), (1525, 1574)),
((1526, 1573), (27, 32), (27, 74), (1526, 1573)),
((1527, 1572), (28, 33), (28, 73), (1527, 1572)),
((1528, 1571), (29, 34), (29, 72), (1528, 1571)),
((1529, 1570), (30, 35), (30, 71), (1529, 1570)),
((1530, 1569), (31, 36), (31, 70), (1530, 1569)),
((1531, 1568), (32, 37), (32, 69), (1531, 1568)),
((1532, 1567), (33, 38), (33, 68), (1532, 1567)),
((1533, 1566), (34, 39), (34, 67), (1533, 1566)),
((1534, 1565), (35, 40), (35, 66), (1534, 1565)),
((1535, 1564), (36, 41), (36, 65), (1535, 1564)),
((1536, 1563), (37, 42), (37, 64), (1536, 1563)),
((1537, 1562), (38, 43), (38, 63), (1537, 1562)),
((1538, 1561), (39, 44), (39, 62), (1538, 1561)),
((1539, 1560), (40, 45), (40, 61), (1539, 1560)),
((1540, 1559), (41, 46), (41, 60), (1540, 1559)),
((1541, 1558), (42, 47), (42, 59), (1541, 1558)),
((1542, 1557), (43, 48), (43, 58), (1542, 1557)),
((1543, 1556), (44, 49), (44, 57), (1543, 1556)),
((1544, 1555), (45, 50), (45, 56), (1544, 1555)),
((1545, 1554), (46, 51), (46, 55), (1545, 1554)),
((1546, 1553), (47, 52), (47, 54), (1546, 1553)),
((1547, 1552), (48, 53), (48, 53), (386, 388)),
((1548, 1551), (49, 54), (49, 52), (387, 387)),
((1549, 1550), (50, 55), (50, 51), (387, 387)),
((1550, 1549), (51, 56), (51, 50), (387, 387)),
((1551, 1548), (52, 57), (52, 49), (387, 387)),
((1552, 1547), (53, 58), (53, 48), (388, 386)),
((1553, 1546), (54, 59), (54, 47), (388, 386)),
((1554, 1545), (55, 60), (55, 46), (388, 386)),
((1555, 1544), (56, 61), (56, 45), (388, 386)),
((1556, 1543), (57, 62), (57, 44), (389, 385)),
((1557, 1542), (58, 63), (58, 43), (389, 385)),
((1558, 1541), (59, 64), (59, 42), (389, 385)),
((1559, 1540), (60, 65), (60, 41), (389, 385)),
((1560, 1539), (61, 66), (61, 40), (390, 384)),
((1561, 1538), (62, 67), (62, 39), (390, 384)),
((1562, 1537), (63, 68), (63, 38), (390, 384)),
((1563, 1536), (64, 69), (64, 37), (390, 384)),
((1564, 1535), (65, 70), (65, 36), (391, 383)),
((1565, 1534), (66, 71), (66, 35), (313, 306)),
((1566, 1533), (67, 72), (67, 34), (313, 306)),
((1567, 1532), (68, 73), (68, 33), (313, 306)),
((1568, 1531), (69, 74), (69, 32), (313, 306)),
((1569, 1530), (70, 75), (70, 31), (313, 306)),
((1570, 1529), (71, 76), (71, 30), (314, 305)),
((1571, 1528), (72, 77), (72, 29), (314, 305)),
((1572, 1527), (73, 78), (73, 28), (314, 305)),
((1573, 1526), (74, 79), (74, 27), (314, 305)),
((1574, 1525), (75, 80), (75, 26), (262, 254)),
((1575, 1524), (76, 81), (76, 25), (262, 254)),
((1576, 1523), (77, 82), (77, 24), (262, 253)),
((1577, 1522), (78, 83), (78, 23), (262, 253)),
((1578, 1521), (79, 84), (79, 22), (263, 253)),
((1579, 1520), (80, 85), (80, 21), (175, 168)),
((1580, 1519), (81, 86), (81, 20), (175, 168)),
((1581, 1518), (82, 87), (82, 19), (175, 168)),
((1582, 1517), (83, 88), (83, 18), (175, 168)),
((1583, 1516), (84, 89), (84, 17), (158, 151)),
((1584, 1515), (85, 90), (85, 16), (158, 151)),
((1585, 1514), (86, 91), (86, 15), (144, 137)),
((1586, 1513), (87, 92), (87, 14), (144, 137)),
((1587, 1512), (88, 93), (88, 13), (132, 126)),
((1588, 1511), (89, 94), (89, 12), (132, 125)),
((1589, 1510), (90, 95), (90, 11), (122, 116)),
((1590, 1509), (91, 96), (91, 10), (99, 94)),
((1591, 1508), (92, 97), (92, 9), (93, 88)),
((1592, 1507), (93, 98), (93, 8), (83, 79)),
((1593, 1506), (94, 99), (94, 7), (75, 71)),
((1594, 1505), (95, 100), (95, 6), (63, 60)),
((1595, 1504), (96, 101), (96, 5), (55, 51)),
((1596, 1503), (97, 102), (97, 4), (44, 41)),
((1597, 1502), (98, 103), (98, 3), (35, 33)),
((1598, 1501), (99, 104), (99, 2), (23, 22)),
((1599, 1500), (100, 105), (100, 1), (12, 11)),
];

for row in table {
let block_max_weight = row.0 .0;
let block_max_size = row.0 .1;
let consensus_slot_numerator = row.1 .0;
let consensus_slot_denominator = row.1 .1;
let bundle_probability_numerator = row.2 .0;
let bundle_probability_denominator = row.2 .1;
let expected_bundle_max_weight = row.3 .0;
let expected_bundle_max_size = row.3 .1;

let domain_bundle_limit = calculate_max_bundle_weight_and_size(
block_max_size,
Weight::from_all(block_max_weight),
(consensus_slot_numerator, consensus_slot_denominator),
(bundle_probability_numerator, bundle_probability_denominator),
)
.unwrap();

assert_eq!(
domain_bundle_limit.max_bundle_size,
expected_bundle_max_size
);
assert_eq!(
domain_bundle_limit.max_bundle_weight,
Weight::from_all(expected_bundle_max_weight)
);
}
}
16 changes: 16 additions & 0 deletions crates/sp-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ impl<Extrinsic: Encode, Number: Encode, Hash: Encode, DomainHeader: HeaderT, Bal
.map(|tx| tx.encoded_size() as u32)
.sum::<u32>()
}

pub fn estimated_weight(&self) -> Weight {
self.sealed_header.header.estimated_bundle_weight
}
}

/// Bundle with opaque extrinsics.
Expand Down Expand Up @@ -962,6 +966,14 @@ pub struct DomainBlockLimit {
pub max_block_weight: Weight,
}

#[derive(Debug, Decode, Encode, TypeInfo, Clone)]
pub struct DomainBundleLimit {
/// The max bundle size for the domain.
pub max_bundle_size: u32,
/// The max bundle weight for the domain.
pub max_bundle_weight: Weight,
}

/// Checks if the signer Id hash is within the tx range
pub fn signer_in_tx_range(bundle_vrf_hash: &U256, signer_id_hash: &U256, tx_range: &U256) -> bool {
let distance_from_vrf_hash = bidirectional_distance(bundle_vrf_hash, signer_id_hash);
Expand Down Expand Up @@ -1178,6 +1190,7 @@ pub type ExecutionReceiptFor<DomainHeader, CBlock, Balance> = ExecutionReceipt<

sp_api::decl_runtime_apis! {
/// API necessary for domains pallet.
#[api_version(2)]
pub trait DomainsApi<DomainHeader: HeaderT> {
/// Submits the transaction bundle via an unsigned extrinsic.
fn submit_bundle_unsigned(opaque_bundle: OpaqueBundle<NumberFor<Block>, Block::Hash, DomainHeader, Balance>);
Expand Down Expand Up @@ -1227,6 +1240,9 @@ sp_api::decl_runtime_apis! {
/// Returns the domain block limit of the given domain.
fn domain_block_limit(domain_id: DomainId) -> Option<DomainBlockLimit>;

/// Returns the domain bundle limit of the given domain.
fn domain_bundle_limit(domain_id: DomainId) -> Option<DomainBundleLimit>;

/// Returns true if there are any ERs in the challenge period with non empty extrinsics.
fn non_empty_er_exists(domain_id: DomainId) -> bool;

Expand Down
Loading

0 comments on commit ea3a4fd

Please sign in to comment.