Skip to content

Commit

Permalink
Merge pull request #2516 from subspace/verify-poe
Browse files Browse the repository at this point in the history
Add check to the bundle slot and the proof-of-time
  • Loading branch information
NingLin-P authored Feb 12, 2024
2 parents a2d382c + ff21e95 commit e146759
Show file tree
Hide file tree
Showing 21 changed files with 401 additions and 229 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/pallet-domains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, git = "https:/
log = { version = "0.4.20", default-features = false }
scale-info = { version = "2.7.0", default-features = false, features = ["derive"] }
sp-consensus-slots = { version = "0.10.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "d6b500960579d73c43fc4ef550b703acfa61c4c8" }
sp-consensus-subspace = { version = "0.1.0", default-features = false, path = "../sp-consensus-subspace" }
sp-core = { version = "21.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "d6b500960579d73c43fc4ef550b703acfa61c4c8" }
sp-domains = { version = "0.1.0", default-features = false, path = "../sp-domains" }
sp-domains-fraud-proof = { version = "0.1.0", default-features = false, path = "../sp-domains-fraud-proof" }
Expand Down Expand Up @@ -50,6 +51,7 @@ std = [
"log/std",
"scale-info/std",
"sp-consensus-slots/std",
"sp-consensus-subspace/std",
"sp-core/std",
"sp-domains/std",
"sp-domains-fraud-proof/std",
Expand Down
65 changes: 57 additions & 8 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ use frame_system::offchain::SubmitTransaction;
use frame_system::pallet_prelude::*;
pub use pallet::*;
use scale_info::TypeInfo;
use sp_consensus_subspace::consensus::is_proof_of_time_valid;
use sp_consensus_subspace::WrappedPotOutput;
use sp_core::H256;
use sp_domains::bundle_producer_election::BundleProducerElectionParams;
use sp_domains::{
Expand All @@ -66,7 +68,7 @@ use sp_std::boxed::Box;
use sp_std::collections::btree_map::BTreeMap;
use sp_std::vec::Vec;
pub use staking::OperatorConfig;
use subspace_core_primitives::U256;
use subspace_core_primitives::{BlockHash, SlotNumber, U256};
use subspace_runtime_primitives::Balance;

pub(crate) type BalanceOf<T> =
Expand All @@ -83,6 +85,11 @@ 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 type ExecutionReceiptOf<T> = ExecutionReceipt<
BlockNumberFor<T>,
<T as frame_system::Config>::Hash,
Expand Down Expand Up @@ -143,8 +150,8 @@ mod pallet {
use crate::staking_epoch::{do_finalize_domain_current_epoch, Error as StakingEpochError};
use crate::weights::WeightInfo;
use crate::{
BalanceOf, DomainBlockNumberFor, ElectionVerificationParams, HoldIdentifier, NominatorId,
OpaqueBundleOf, ReceiptHashFor, STORAGE_VERSION,
BalanceOf, BlockSlot, DomainBlockNumberFor, ElectionVerificationParams, HoldIdentifier,
NominatorId, OpaqueBundleOf, ReceiptHashFor, STORAGE_VERSION,
};
use alloc::string::String;
use codec::FullCodec;
Expand Down Expand Up @@ -311,6 +318,9 @@ mod pallet {

/// Storage fee interface used to deal with bundle storage fee
type StorageFee: StorageFee<BalanceOf<Self>>;

/// The block slot
type BlockSlot: BlockSlot;
}

#[pallet::pallet]
Expand Down Expand Up @@ -585,10 +595,16 @@ mod pallet {
Receipt(BlockTreeError),
/// Bundle size exceed the max bundle size limit in the domain config
BundleTooLarge,
// Bundle with an invalid extrinsic root
/// Bundle with an invalid extrinsic root
InvalidExtrinsicRoot,
/// This bundle duplicated with an already submitted bundle
DuplicatedBundle,
/// Invalid proof of time in the proof of election
InvalidProofOfTime,
/// The bundle is built on a slot in the future
SlotInTheFuture,
/// The bundle is built on a slot in the past
SlotInThePast,
}

#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
Expand Down Expand Up @@ -1380,7 +1396,7 @@ mod pallet {
type Call = Call<T>;
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
match call {
Call::submit_bundle { opaque_bundle } => Self::validate_bundle(opaque_bundle)
Call::submit_bundle { opaque_bundle } => Self::validate_bundle(opaque_bundle, true)
.map_err(|_| InvalidTransaction::Call.into())
.and_then(|_| {
charge_bundle_storage_fee::<T>(
Expand All @@ -1398,15 +1414,17 @@ mod pallet {
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
match call {
Call::submit_bundle { opaque_bundle } => {
if let Err(e) = Self::validate_bundle(opaque_bundle) {
if let Err(e) = Self::validate_bundle(opaque_bundle, false) {
match e {
// These errors are common due to networking delay or chain re-org,
// using a lower log level to avoid the noise.
BundleError::Receipt(BlockTreeError::InFutureReceipt)
| BundleError::Receipt(BlockTreeError::StaleReceipt)
| BundleError::Receipt(BlockTreeError::NewBranchReceipt)
| BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
| BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock) => {
| BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
| BundleError::SlotInThePast
| BundleError::SlotInTheFuture => {
log::debug!(
target: "runtime::domains",
"Bad bundle {:?}, error: {e:?}", opaque_bundle.domain_id(),
Expand Down Expand Up @@ -1571,7 +1589,10 @@ impl<T: Config> Pallet<T> {
Ok(())
}

fn validate_bundle(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
fn validate_bundle(
opaque_bundle: &OpaqueBundleOf<T>,
pre_dispatch: bool,
) -> Result<(), BundleError> {
let domain_id = opaque_bundle.domain_id();
let operator_id = opaque_bundle.operator_id();
let sealed_header = &opaque_bundle.sealed_header;
Expand Down Expand Up @@ -1606,9 +1627,37 @@ 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);
}

sp_domains::bundle_producer_election::check_proof_of_election(
&operator.signing_key,
domain_config.bundle_slot_probability,
Expand Down
134 changes: 14 additions & 120 deletions crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::block_tree::BlockTreeNode;
use crate::domain_registry::{DomainConfig, DomainObject};
use crate::staking::Operator;
use crate::{
self as pallet_domains, BalanceOf, BlockTree, BlockTreeNodes, BundleError, Config,
self as pallet_domains, BalanceOf, BlockSlot, BlockTree, BlockTreeNodes, BundleError, Config,
ConsensusBlockHash, DomainBlockNumberFor, DomainHashingFor, DomainRegistry, ExecutionInbox,
ExecutionReceiptOf, FraudProofError, FungibleHoldId, HeadReceiptNumber, NextDomainId,
OperatorStatus, Operators, ReceiptHashFor,
Expand Down Expand Up @@ -48,7 +48,6 @@ use sp_std::sync::Arc;
use sp_trie::trie_types::TrieDBMutBuilderV1;
use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut};
use sp_version::RuntimeVersion;
use std::sync::atomic::{AtomicU64, Ordering};
use subspace_core_primitives::{Randomness, U256 as P256};
use subspace_runtime_primitives::{Moment, StorageFee, SSC};

Expand Down Expand Up @@ -116,23 +115,10 @@ parameter_types! {
pub const BlockTreePruningDepth: u32 = 16;
}

static CONFIRMATION_DEPTH_K: AtomicU64 = AtomicU64::new(10);

pub struct ConfirmationDepthK;

impl ConfirmationDepthK {
fn set(new: BlockNumber) {
CONFIRMATION_DEPTH_K.store(new, Ordering::SeqCst);
}

fn get() -> BlockNumber {
CONFIRMATION_DEPTH_K.load(Ordering::SeqCst)
}
}

impl Get<BlockNumber> for ConfirmationDepthK {
fn get() -> BlockNumber {
Self::get()
10
}
}

Expand Down Expand Up @@ -223,6 +209,17 @@ impl StorageFee<Balance> for DummyStorageFee {
fn note_storage_fees(_fee: Balance) {}
}

pub struct DummyBlockSlot;
impl BlockSlot for DummyBlockSlot {
fn current_slot() -> sp_consensus_slots::Slot {
0u64.into()
}

fn future_slot() -> sp_consensus_slots::Slot {
0u64.into()
}
}

impl pallet_domains::Config for Test {
type RuntimeEvent = RuntimeEvent;
type DomainHash = sp_core::H256;
Expand Down Expand Up @@ -252,6 +249,7 @@ impl pallet_domains::Config for Test {
type SudoId = ();
type PalletId = DomainsPalletId;
type StorageFee = DummyStorageFee;
type BlockSlot = DummyBlockSlot;
}

pub struct ExtrinsicStorageFees;
Expand Down Expand Up @@ -646,110 +644,6 @@ pub(crate) fn get_block_tree_node_at<T: Config>(
BlockTree::<T>::get(domain_id, block_number).and_then(BlockTreeNodes::<T>::get)
}

// TODO: Unblock once bundle producer election v2 is finished.
#[test]
#[ignore]
fn test_stale_bundle_should_be_rejected() {
// Small macro in order to be more readable.
//
// We only care about whether the error type is `StaleBundle`.
macro_rules! assert_stale {
($validate_bundle_result:expr) => {
assert_eq!(
$validate_bundle_result,
Err(pallet_domains::BundleError::StaleBundle)
)
};
}

macro_rules! assert_not_stale {
($validate_bundle_result:expr) => {
assert_ne!(
$validate_bundle_result,
Err(pallet_domains::BundleError::StaleBundle)
)
};
}

ConfirmationDepthK::set(1);
new_test_ext().execute_with(|| {
// Create a bundle at genesis block -> #1
let bundle0 = create_dummy_bundle(DOMAIN_ID, 0, System::parent_hash());
System::initialize(&1, &System::parent_hash(), &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(1);
assert_not_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle0));

// Create a bundle at block #1 -> #2
let block_hash1 = Hash::random();
let bundle1 = create_dummy_bundle(DOMAIN_ID, 1, block_hash1);
System::initialize(&2, &block_hash1, &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(2);
assert_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle0));
assert_not_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle1));
});

ConfirmationDepthK::set(2);
new_test_ext().execute_with(|| {
// Create a bundle at genesis block -> #1
let bundle0 = create_dummy_bundle(DOMAIN_ID, 0, System::parent_hash());
System::initialize(&1, &System::parent_hash(), &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(1);
assert_not_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle0));

// Create a bundle at block #1 -> #2
let block_hash1 = Hash::random();
let bundle1 = create_dummy_bundle(DOMAIN_ID, 1, block_hash1);
System::initialize(&2, &block_hash1, &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(2);
assert_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle0));
assert_not_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle1));

// Create a bundle at block #2 -> #3
let block_hash2 = Hash::random();
let bundle2 = create_dummy_bundle(DOMAIN_ID, 2, block_hash2);
System::initialize(&3, &block_hash2, &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(3);
assert_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle0));
assert_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle1));
assert_not_stale!(pallet_domains::Pallet::<Test>::validate_bundle(&bundle2));
});

ConfirmationDepthK::set(10);
let confirmation_depth_k = ConfirmationDepthK::get();
let (dummy_bundles, block_hashes): (Vec<_>, Vec<_>) = (1..=confirmation_depth_k + 2)
.map(|n| {
let consensus_block_hash = Hash::random();
(
create_dummy_bundle(DOMAIN_ID, n, consensus_block_hash),
consensus_block_hash,
)
})
.unzip();

let run_to_block = |n: BlockNumber, block_hashes: Vec<Hash>| {
System::initialize(&1, &System::parent_hash(), &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(1);
System::finalize();

for b in 2..=n {
System::set_block_number(b);
System::initialize(&b, &block_hashes[b as usize - 2], &Default::default());
<Domains as Hooks<BlockNumber>>::on_initialize(b);
System::finalize();
}
};

new_test_ext().execute_with(|| {
run_to_block(confirmation_depth_k + 2, block_hashes);
for bundle in dummy_bundles.iter().take(2) {
assert_stale!(pallet_domains::Pallet::<Test>::validate_bundle(bundle));
}
for bundle in dummy_bundles.iter().skip(2) {
assert_not_stale!(pallet_domains::Pallet::<Test>::validate_bundle(bundle));
}
});
}

#[test]
fn test_calculate_tx_range() {
let cur_tx_range = P256::from(400_u64);
Expand Down
3 changes: 2 additions & 1 deletion crates/sc-consensus-subspace-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ where
}

let global_challenge = new_slot_info
.global_randomness
.proof_of_time
.derive_global_randomness()
.derive_global_challenge(slot_number);

// This will be sent to the farmer
Expand Down
11 changes: 5 additions & 6 deletions crates/sc-consensus-subspace/src/slot_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use subspace_core_primitives::{
BlockNumber, PotCheckpoints, PotOutput, PublicKey, Randomness, RewardSignature, SectorId,
Solution, SolutionRange, REWARD_SIGNING_CONTEXT,
BlockNumber, PotCheckpoints, PotOutput, PublicKey, RewardSignature, SectorId, Solution,
SolutionRange, REWARD_SIGNING_CONTEXT,
};
use subspace_proof_of_space::Table;
use subspace_verification::{
Expand Down Expand Up @@ -136,8 +136,8 @@ where
pub struct NewSlotInfo {
/// Slot
pub slot: Slot,
/// Global randomness
pub global_randomness: Randomness,
/// The PoT output for `slot`
pub proof_of_time: PotOutput,
/// Acceptable solution range for block authoring
pub solution_range: SolutionRange,
/// Acceptable solution range for voting
Expand Down Expand Up @@ -272,7 +272,6 @@ where
}

let proof_of_time = checkpoints.output();
let global_randomness = proof_of_time.derive_global_randomness();

// NOTE: Best hash is not necessarily going to be the parent of corresponding block, but
// solution range shouldn't be too far off
Expand All @@ -293,7 +292,7 @@ where

let new_slot_info = NewSlotInfo {
slot,
global_randomness,
proof_of_time,
solution_range,
voting_solution_range,
};
Expand Down
Loading

0 comments on commit e146759

Please sign in to comment.