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

Introduce bundle longevity #2586

Merged
merged 6 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion crates/pallet-domains/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use sp_domains::{
dummy_opaque_bundle, DomainId, ExecutionReceipt, OperatorAllowList, OperatorId,
OperatorPublicKey, RuntimeType,
};
use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, One, Zero};
use sp_runtime::traits::{CheckedAdd, One, Zero};

const SEED: u32 = 0;

Expand Down
114 changes: 81 additions & 33 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ use sp_domains_fraud_proof::verification::{
verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
};
use sp_runtime::traits::{Hash, Header, One, Zero};
use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
pub use staking::OperatorConfig;
use subspace_core_primitives::{BlockHash, SlotNumber, U256};
use subspace_core_primitives::{BlockHash, PotOutput, SlotNumber, U256};
use subspace_runtime_primitives::Balance;

pub(crate) type BalanceOf<T> =
Expand All @@ -89,9 +89,12 @@ pub trait HoldIdentifier<T: Config> {
fn storage_fund_withdrawal(operator_id: OperatorId) -> FungibleHoldId<T>;
}

pub trait BlockSlot {
fn current_slot() -> sp_consensus_slots::Slot;
fn future_slot() -> sp_consensus_slots::Slot;
pub trait BlockSlot<T: frame_system::Config> {
// Return the future slot of the given `block_number`
fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;

// Return the latest block number whose slot is less than the given `to_check` slot
fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
}

pub type ExecutionReceiptOf<T> = ExecutionReceipt<
Expand Down Expand Up @@ -338,7 +341,7 @@ mod pallet {
type StorageFee: StorageFee<BalanceOf<Self>>;

/// The block slot
type BlockSlot: BlockSlot;
type BlockSlot: BlockSlot<Self>;

/// Transfers tracker.
type DomainsTransfersTracker: DomainsTransfersTracker<BalanceOf<Self>>;
Expand All @@ -349,6 +352,10 @@ mod pallet {
/// Minimum balance for each initial domain account
type MinInitialDomainAccountBalance: Get<BalanceOf<Self>>;

/// How many block a bundle should still consider as valid after produced
#[pallet::constant]
type BundleLongevity: Get<u32>;

/// Post hook to notify accepted domain bundles in previous block.
type DomainBundleSubmitted: DomainBundleSubmitted;
}
Expand Down Expand Up @@ -1610,6 +1617,69 @@ impl<T: Config> Pallet<T> {
Ok(())
}

fn check_slot_and_proof_of_time(
slot_number: u64,
proof_of_time: PotOutput,
pre_dispatch: bool,
) -> Result<(), BundleError> {
// NOTE: the `current_block_number` from `frame_system` is initialized during `validate_unsigned` thus
// it is the same value in both `validate_unsigned` and `pre_dispatch`
let current_block_number = frame_system::Pallet::<T>::current_block_number();

// Check if the slot is in future
//
// NOTE: during `validate_unsigned` this is implicitly checked within `is_proof_of_time_valid` since we
// are using quick verification which will return `false` if the `proof-of-time` is not seem by the node
// before.
if pre_dispatch {
if let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
}
}

// Check if the bundle is built too long time ago and beyond `T::BundleLongevity` number of consensus blocks.
let produced_after_block_number =
match T::BlockSlot::slot_produced_after(slot_number.into()) {
Some(n) => n,
None => {
// There is no slot for the genesis block, if the current block is less than `BundleLongevity`
// than we assume the slot is produced after the genesis block.
if current_block_number > T::BundleLongevity::get().into() {
return Err(BundleError::SlotInThePast);
} else {
Zero::zero()
}
}
};
let produced_after_block_hash = if produced_after_block_number == current_block_number {
// The hash of the current block is only available in the next block thus use the parent hash here
frame_system::Pallet::<T>::parent_hash()
} else {
frame_system::Pallet::<T>::block_hash(produced_after_block_number)
};
NingLin-P marked this conversation as resolved.
Show resolved Hide resolved
if let Some(last_eligible_block) =
current_block_number.checked_sub(&T::BundleLongevity::get().into())
{
ensure!(
produced_after_block_number >= last_eligible_block,
BundleError::SlotInThePast
);
}

if !is_proof_of_time_valid(
BlockHash::try_from(produced_after_block_hash.as_ref())
.expect("Must be able to convert to block hash type"),
SlotNumber::from(slot_number),
WrappedPotOutput::from(proof_of_time),
// Quick verification when entering transaction pool, but not when constructing the block
!pre_dispatch,
) {
return Err(BundleError::InvalidProofOfTime);
}

Ok(())
}

fn validate_bundle(
opaque_bundle: &OpaqueBundleOf<T>,
pre_dispatch: bool,
Expand Down Expand Up @@ -1658,36 +1728,14 @@ impl<T: Config> Pallet<T> {
Self::check_extrinsics_root(opaque_bundle)?;

let proof_of_election = &sealed_header.header.proof_of_election;
let slot_number = proof_of_election.slot_number;
let (operator_stake, total_domain_stake) =
Self::fetch_operator_stake_info(domain_id, &operator_id)?;

// Check if the bundle is built with slot and valid proof-of-time that produced between the parent block
// and the current block
//
// NOTE: during `validate_unsigned` `current_slot` is query from the parent block and during `pre_dispatch`
// the `future_slot` is query from the current block.
if pre_dispatch {
ensure!(
slot_number <= *T::BlockSlot::future_slot(),
BundleError::SlotInTheFuture
)
} else {
ensure!(
slot_number > *T::BlockSlot::current_slot(),
BundleError::SlotInThePast
);
}
if !is_proof_of_time_valid(
BlockHash::try_from(frame_system::Pallet::<T>::parent_hash().as_ref())
.expect("Must be able to convert to block hash type"),
SlotNumber::from(slot_number),
WrappedPotOutput::from(proof_of_election.proof_of_time),
// Quick verification when entering transaction pool, but not when constructing the block
!pre_dispatch,
) {
return Err(BundleError::InvalidProofOfTime);
}
Self::check_slot_and_proof_of_time(
proof_of_election.slot_number,
proof_of_election.proof_of_time,
pre_dispatch,
)?;

sp_domains::bundle_producer_election::check_proof_of_election(
&operator.signing_key,
Expand Down
12 changes: 7 additions & 5 deletions crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ parameter_types! {
pub const DomainChainByteFee: Balance = 1;
pub const MaxInitialDomainAccounts: u32 = 5;
pub const MinInitialDomainAccountBalance: Balance = SSC;
pub const BundleLongevity: u32 = 5;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why cannot we use the BlockSlotCount instead of creating a new type in config ?

Copy link
Member Author

Choose a reason for hiding this comment

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

The BlockSlotCount needs to be greater than BundleLongevity, and BlockSlotCount is in pallet-subspace we need to add a config item in pallet-domains anyway.


pub struct MockRandomness;
Expand Down Expand Up @@ -218,13 +219,13 @@ impl StorageFee<Balance> for DummyStorageFee {

pub struct DummyBlockSlot;

impl BlockSlot for DummyBlockSlot {
fn current_slot() -> sp_consensus_slots::Slot {
0u64.into()
impl BlockSlot<Test> for DummyBlockSlot {
fn future_slot(_block_number: BlockNumberFor<Test>) -> Option<sp_consensus_slots::Slot> {
Some(0u64.into())
}

fn future_slot() -> sp_consensus_slots::Slot {
0u64.into()
fn slot_produced_after(_slot: sp_consensus_slots::Slot) -> Option<BlockNumberFor<Test>> {
Some(0u64)
}
}

Expand Down Expand Up @@ -310,6 +311,7 @@ impl pallet_domains::Config for Test {
type DomainsTransfersTracker = MockDomainsTransfersTracker;
type MaxInitialDomainAccounts = MaxInitialDomainAccounts;
type MinInitialDomainAccountBalance = MinInitialDomainAccountBalance;
type BundleLongevity = BundleLongevity;
type ConsensusSlotProbability = SlotProbability;
type DomainBundleSubmitted = ();
}
Expand Down
20 changes: 20 additions & 0 deletions crates/pallet-subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ pub mod pallet {

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

/// Maximum number of block number to block slot mappings to keep (oldest pruned first).
#[pallet::constant]
type BlockSlotCount: Get<u32>;
}

#[derive(Debug, Default, Encode, Decode, TypeInfo)]
Expand Down Expand Up @@ -371,11 +375,18 @@ pub mod pallet {
#[pallet::getter(fn genesis_slot)]
pub type GenesisSlot<T> = StorageValue<_, Slot, ValueQuery>;

// TODO: Replace `CurrentSlot` with `BlockSlots`
/// Current slot number.
#[pallet::storage]
#[pallet::getter(fn current_slot)]
pub type CurrentSlot<T> = StorageValue<_, Slot, ValueQuery>;

/// Bounded mapping from block number to slot
#[pallet::storage]
#[pallet::getter(fn block_slots)]
pub type BlockSlots<T: Config> =
StorageValue<_, BoundedBTreeMap<BlockNumberFor<T>, Slot, T::BlockSlotCount>, ValueQuery>;

// TODO: Clarify when this value is updated (when it is updated, right now it is not)
/// Number of iterations for proof of time per slot
#[pallet::storage]
Expand Down Expand Up @@ -782,6 +793,15 @@ impl<T: Config> Pallet<T> {
// The slot number of the current block being initialized.
CurrentSlot::<T>::put(pre_digest.slot());

BlockSlots::<T>::mutate(|block_slots| {
if let Some(to_remove) = block_number.checked_sub(&T::BlockSlotCount::get().into()) {
block_slots.remove(&to_remove);
}
block_slots
.try_insert(block_number, pre_digest.slot())
.expect("one entry just removed before inserting; qed");
});

{
let farmer_public_key = pre_digest.solution().public_key.clone();

Expand Down
2 changes: 2 additions & 0 deletions crates/pallet-subspace/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ parameter_types! {
pub const ReplicationFactor: u16 = 1;
pub const ReportLongevity: u64 = 34;
pub const ShouldAdjustSolutionRange: bool = false;
pub const BlockSlotCount: u32 = 6;
}

impl Config for Test {
Expand All @@ -196,6 +197,7 @@ impl Config for Test {
type MaxPiecesInSector = ConstU16<{ MAX_PIECES_IN_SECTOR }>;
type ShouldAdjustSolutionRange = ShouldAdjustSolutionRange;
type EraChangeTrigger = NormalEraChange;
type BlockSlotCount = BlockSlotCount;

type HandleEquivocation = EquivocationHandler<OffencesSubspace, ReportLongevity>;

Expand Down
31 changes: 26 additions & 5 deletions crates/subspace-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ parameter_types! {
// Disable solution range adjustment at the start of chain.
// Root origin must enable later
pub const ShouldAdjustSolutionRange: bool = false;
pub const BlockSlotCount: u32 = 6;
NingLin-P marked this conversation as resolved.
Show resolved Hide resolved
}

pub struct ConfirmationDepthK;
Expand Down Expand Up @@ -348,6 +349,7 @@ impl pallet_subspace::Config for Runtime {
type MaxPiecesInSector = ConstU16<{ MAX_PIECES_IN_SECTOR }>;
type ShouldAdjustSolutionRange = ShouldAdjustSolutionRange;
type EraChangeTrigger = pallet_subspace::NormalEraChange;
type BlockSlotCount = BlockSlotCount;

type HandleEquivocation = pallet_subspace::equivocation::EquivocationHandler<
OffencesSubspace,
Expand Down Expand Up @@ -597,8 +599,17 @@ parameter_types! {
pub const DomainsPalletId: PalletId = PalletId(*b"domains_");
pub const MaxInitialDomainAccounts: u32 = 10;
pub const MinInitialDomainAccountBalance: Balance = SSC;
pub const BundleLongevity: u32 = 5;
}

// `BlockSlotCount` must at least keep the slot for the current and the parent block, it also need to
// keep enough block slot for bundle validation
const_assert!(BlockSlotCount::get() >= 2 && BlockSlotCount::get() > BundleLongevity::get());

// `BlockHashCount` must greater than `BlockSlotCount` because we need to use the block number found
// with `BlockSlotCount` to get the block hash.
const_assert!(BlockHashCount::get() > BlockSlotCount::get());

// Minimum operator stake must be >= minimum nominator stake since operator is also a nominator.
const_assert!(MinOperatorStake::get() >= MinNominatorStake::get());

Expand All @@ -607,13 +618,22 @@ const_assert!(StakeWithdrawalLockingPeriod::get() >= BlockTreePruningDepth::get(

pub struct BlockSlot;

impl pallet_domains::BlockSlot for BlockSlot {
fn current_slot() -> sp_consensus_slots::Slot {
Subspace::current_slot()
impl pallet_domains::BlockSlot<Runtime> for BlockSlot {
fn future_slot(block_number: BlockNumber) -> Option<sp_consensus_slots::Slot> {
let block_slots = Subspace::block_slots();
block_slots
.get(&block_number)
.map(|slot| *slot + Slot::from(BlockAuthoringDelay::get()))
}

fn future_slot() -> sp_consensus_slots::Slot {
Subspace::current_slot() + Slot::from(BlockAuthoringDelay::get())
fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumber> {
let block_slots = Subspace::block_slots();
for (block_number, slot) in block_slots.into_iter().rev() {
if to_check > slot {
return Some(block_number);
}
}
None
}
}

Expand Down Expand Up @@ -648,6 +668,7 @@ impl pallet_domains::Config for Runtime {
type PalletId = DomainsPalletId;
type StorageFee = TransactionFees;
type BlockSlot = BlockSlot;
type BundleLongevity = BundleLongevity;
type DomainsTransfersTracker = Transporter;
type MaxInitialDomainAccounts = MaxInitialDomainAccounts;
type MinInitialDomainAccountBalance = MinInitialDomainAccountBalance;
Expand Down
Loading
Loading