Skip to content

Commit

Permalink
Merge pull request #2687 from subspace/stateless-mmr-proof
Browse files Browse the repository at this point in the history
Stateless mmr proof for the consensus chain
  • Loading branch information
NingLin-P authored Apr 19, 2024
2 parents f059499 + ff17a5f commit 13a6380
Show file tree
Hide file tree
Showing 27 changed files with 484 additions and 193 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

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

25 changes: 24 additions & 1 deletion crates/pallet-subspace-mmr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use frame_system::pallet_prelude::BlockNumberFor;
pub use pallet::*;
use sp_core::Get;
use sp_mmr_primitives::{LeafDataProvider, OnNewRoot};
use sp_runtime::traits::{CheckedSub, One};
use sp_runtime::DigestItem;
Expand All @@ -28,22 +29,44 @@ use sp_subspace_mmr::{LeafDataV0, MmrDigest, MmrLeaf};

#[frame_support::pallet]
mod pallet {
use frame_support::pallet_prelude::*;
use frame_support::Parameter;
use frame_system::pallet_prelude::BlockNumberFor;
use sp_core::H256;

#[pallet::pallet]
pub struct Pallet<T>(_);

#[pallet::config]
pub trait Config: frame_system::Config<Hash: Into<H256> + From<H256>> {
type MmrRootHash: Parameter + Copy;
type MmrRootHash: Parameter + Copy + MaxEncodedLen;

/// The number of mmr root hashes to store in the runtime. It will be used to verify mmr
/// proof statelessly and the number of roots stored here represents the number of blocks
/// for which the mmr proof is valid since it is generated. After that the mmr proof
/// will be expired and the prover needs to re-generate the proof.
type MmrRootHashCount: Get<u32>;
}

/// Map of block numbers to mmr root hashes.
#[pallet::storage]
#[pallet::getter(fn mmr_root_hash)]
pub type MmrRootHashes<T: Config> =
StorageMap<_, Twox64Concat, BlockNumberFor<T>, T::MmrRootHash, OptionQuery>;
}

impl<T: Config> OnNewRoot<T::MmrRootHash> for Pallet<T> {
fn on_new_root(root: &T::MmrRootHash) {
// TODO: this digest is not used remove it before next network reset but keep it
// as is for now to keep compatible with gemini-3h.
let digest = DigestItem::new_mmr_root(*root);
<frame_system::Pallet<T>>::deposit_log(digest);

let block_number = frame_system::Pallet::<T>::block_number();
<MmrRootHashes<T>>::insert(block_number, *root);
if let Some(to_prune) = block_number.checked_sub(&T::MmrRootHashCount::get().into()) {
<MmrRootHashes<T>>::remove(to_prune);
}
}
}

Expand Down
47 changes: 47 additions & 0 deletions crates/sp-subspace-mmr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub use runtime_interface::{domain_mmr_runtime_interface, subspace_mmr_runtime_i

use codec::{Codec, Decode, Encode};
use scale_info::TypeInfo;
use sp_mmr_primitives::{EncodableOpaqueLeaf, Proof as MmrProof};
use sp_runtime::generic::OpaqueDigestItemId;
use sp_runtime::DigestItem;

Expand Down Expand Up @@ -81,3 +82,49 @@ impl<MmrRootHash: Codec> MmrDigest<MmrRootHash> for DigestItem {
}
}
}

/// Consensus chain MMR leaf and its Proof at specific block.
///
/// The verifier is not required to contains any the MMR offchain data but this proof
/// will be expired after `N` blocks where `N` is the number of MMR root stored in the
// consensus chain runtime.
#[derive(Debug, Encode, Decode, Eq, PartialEq, TypeInfo)]
pub struct ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash> {
/// Consensus block info from which this proof was generated.
pub consensus_block_number: CBlockNumber,
pub consensus_block_hash: CBlockHash,
/// Encoded MMR leaf
pub opaque_mmr_leaf: EncodableOpaqueLeaf,
/// MMR proof for the leaf above.
pub proof: MmrProof<MmrHash>,
}

// TODO: update upstream `EncodableOpaqueLeaf` to derive clone.
impl<CBlockNumber: Clone, CBlockHash: Clone, MmrHash: Clone> Clone
for ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash>
{
fn clone(&self) -> Self {
Self {
consensus_block_number: self.consensus_block_number.clone(),
consensus_block_hash: self.consensus_block_hash.clone(),
opaque_mmr_leaf: EncodableOpaqueLeaf(self.opaque_mmr_leaf.0.clone()),
proof: self.proof.clone(),
}
}
}

/// Trait to verify MMR proofs
pub trait MmrProofVerifier<MmrHash, CBlockNumber, CBlockHash> {
/// Returns consensus state root if the given MMR proof is valid
fn verify_proof_and_extract_consensus_state_root(
mmr_leaf_proof: ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash>,
) -> Option<CBlockHash>;
}

impl<MmrHash, CBlockNumber, CBlockHash> MmrProofVerifier<MmrHash, CBlockNumber, CBlockHash> for () {
fn verify_proof_and_extract_consensus_state_root(
_mmr_leaf_proof: ConsensusChainMmrLeafProof<CBlockNumber, CBlockHash, MmrHash>,
) -> Option<CBlockHash> {
None
}
}
1 change: 1 addition & 0 deletions crates/subspace-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ std = [
"sp-std/std",
"sp-subspace-mmr/std",
"sp-transaction-pool/std",
"sp-subspace-mmr/std",
"sp-version/std",
"subspace-core-primitives/std",
"subspace-runtime-primitives/std",
Expand Down
44 changes: 33 additions & 11 deletions crates/subspace-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ use sp_messenger::messages::{
BlockMessagesWithStorageKey, ChainId, CrossDomainMessage, MessageId, MessageKey,
};
use sp_messenger_host_functions::{get_storage_key, StorageKeyRequest};
use sp_mmr_primitives::{EncodableOpaqueLeaf, Proof};
use sp_mmr_primitives::EncodableOpaqueLeaf;
use sp_runtime::traits::{
AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Keccak256, NumberFor,
};
Expand All @@ -85,6 +85,7 @@ use sp_std::collections::btree_map::BTreeMap;
use sp_std::marker::PhantomData;
use sp_std::prelude::*;
use sp_subspace_mmr::subspace_mmr_runtime_interface::consensus_block_hash;
use sp_subspace_mmr::ConsensusChainMmrLeafProof;
use sp_version::RuntimeVersion;
use static_assertions::const_assert;
use subspace_core_primitives::objects::BlockObjectMapping;
Expand Down Expand Up @@ -504,15 +505,31 @@ impl sp_messenger::OnXDMRewards<Balance> for OnXDMRewards {

pub struct MmrProofVerifier;

impl sp_messenger::MmrProofVerifier<mmr::Hash, Hash> for MmrProofVerifier {
impl sp_subspace_mmr::MmrProofVerifier<mmr::Hash, NumberFor<Block>, Hash> for MmrProofVerifier {
fn verify_proof_and_extract_consensus_state_root(
opaque_leaf: EncodableOpaqueLeaf,
proof: Proof<mmr::Hash>,
mmr_leaf_proof: ConsensusChainMmrLeafProof<NumberFor<Block>, Hash, mmr::Hash>,
) -> Option<Hash> {
let leaf: mmr::Leaf = opaque_leaf.into_opaque_leaf().try_decode()?;
let state_root = leaf.state_root();
Mmr::verify_leaves(vec![leaf], proof).ok()?;
Some(state_root)
let ConsensusChainMmrLeafProof {
consensus_block_number,
opaque_mmr_leaf,
proof,
..
} = mmr_leaf_proof;

let mmr_root = SubspaceMmr::mmr_root_hash(consensus_block_number)?;

pallet_mmr::verify_leaves_proof::<mmr::Hashing, _>(
mmr_root,
vec![mmr::DataOrHash::Data(
EncodableOpaqueLeaf(opaque_mmr_leaf.0.clone()).into_opaque_leaf(),
)],
proof,
)
.ok()?;

let leaf: mmr::Leaf = opaque_mmr_leaf.into_opaque_leaf().try_decode()?;

Some(leaf.state_root())
}
}

Expand Down Expand Up @@ -763,8 +780,13 @@ impl pallet_mmr::Config for Runtime {
type WeightInfo = ();
}

parameter_types! {
pub const MmrRootHashCount: u32 = 1024;
}

impl pallet_subspace_mmr::Config for Runtime {
type MmrRootHash = mmr::Hash;
type MmrRootHashCount = MmrRootHashCount;
}

construct_runtime!(
Expand Down Expand Up @@ -1337,16 +1359,16 @@ impl_runtime_apis! {
}
}

impl sp_messenger::RelayerApi<Block, BlockNumber, <Block as BlockT>::Hash> for Runtime {
impl sp_messenger::RelayerApi<Block, BlockNumber, BlockNumber, <Block as BlockT>::Hash> for Runtime {
fn block_messages() -> BlockMessagesWithStorageKey {
Messenger::get_block_messages()
}

fn outbox_message_unsigned(msg: CrossDomainMessage<<Block as BlockT>::Hash, <Block as BlockT>::Hash>) -> Option<<Block as BlockT>::Extrinsic> {
fn outbox_message_unsigned(msg: CrossDomainMessage<NumberFor<Block>, <Block as BlockT>::Hash, <Block as BlockT>::Hash>) -> Option<<Block as BlockT>::Extrinsic> {
Messenger::outbox_message_unsigned(msg)
}

fn inbox_response_message_unsigned(msg: CrossDomainMessage<<Block as BlockT>::Hash, <Block as BlockT>::Hash>) -> Option<<Block as BlockT>::Extrinsic> {
fn inbox_response_message_unsigned(msg: CrossDomainMessage<NumberFor<Block>, <Block as BlockT>::Hash, <Block as BlockT>::Hash>) -> Option<<Block as BlockT>::Extrinsic> {
Messenger::inbox_response_message_unsigned(msg)
}

Expand Down
4 changes: 3 additions & 1 deletion domains/client/domain-operator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ sp-domain-digests = { version = "0.1.0", path = "../../primitives/digests" }
sp-inherents = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-keystore = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-messenger = { version = "0.1.0", path = "../../primitives/messenger" }
sp-mmr-primitives = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-runtime = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-state-machine = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-subspace-mmr = { version = "0.1.0", default-features = false, path = "../../../crates/sp-subspace-mmr" }
sp-transaction-pool = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-trie = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-weights = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
Expand All @@ -54,9 +56,9 @@ pallet-transporter = { version = "0.1.0", path = "../../../domains/pallets/trans
sc-cli = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f", default-features = false }
sc-service = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f", default-features = false }
sc-transaction-pool = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-mmr-primitives = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
sp-state-machine = { git = "https://github.com/subspace/polkadot-sdk", rev = "ac2f3efb476ee3f5ac6bafefb458e9be158adb7f" }
subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../../../crates/subspace-core-primitives" }
subspace-test-runtime = { version = "0.1.0", path = "../../../test/subspace-test-runtime" }
subspace-test-service = { version = "0.1.0", path = "../../../test/subspace-test-service" }
subspace-test-primitives = { version = "0.1.0", path = "../../../test/subspace-test-primitives" }
tempfile = "3.10.1"
54 changes: 54 additions & 0 deletions domains/client/domain-operator/src/domain_block_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ use sp_domains::{BundleValidity, DomainId, DomainsApi, ExecutionReceipt, HeaderH
use sp_domains_fraud_proof::fraud_proof::{FraudProof, ValidBundleProof};
use sp_domains_fraud_proof::FraudProofApi;
use sp_messenger::MessengerApi;
use sp_mmr_primitives::MmrApi;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One, Zero};
use sp_runtime::{Digest, Saturating};
use sp_subspace_mmr::ConsensusChainMmrLeafProof;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::str::FromStr;
Expand Down Expand Up @@ -977,6 +979,58 @@ where
}
}

/// Generate MMR proof for the block `to_prove` in the current best fork. The returned proof
/// can be later used to verify stateless (without query offchain MMR leaf) and extract the state
/// root at `to_prove`.
// TODO: remove `dead_code` after it is used in fraud proof generation
#[allow(dead_code)]
pub(crate) fn generate_mmr_proof<CClient, CBlock>(
consensus_client: &Arc<CClient>,
to_prove: NumberFor<CBlock>,
) -> sp_blockchain::Result<ConsensusChainMmrLeafProof<NumberFor<CBlock>, CBlock::Hash, H256>>
where
CBlock: BlockT,
CClient: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + 'static,
CClient::Api: MmrApi<CBlock, H256, NumberFor<CBlock>>,
{
let api = consensus_client.runtime_api();
let prove_at_hash = consensus_client.info().best_hash;
let prove_at_number = consensus_client.info().best_number;

if to_prove >= prove_at_number {
return Err(sp_blockchain::Error::Application(Box::from(format!(
"Can't generate MMR proof for block {to_prove:?} >= best block {prove_at_number:?}"
))));
}

let (mut leaves, proof) = api
// NOTE: the mmr leaf data is added in the next block so to generate the MMR proof of
// block `to_prove` we need to use `to_prove + 1` here.
.generate_proof(
prove_at_hash,
vec![to_prove + One::one()],
Some(prove_at_number),
)?
.map_err(|err| {
sp_blockchain::Error::Application(Box::from(format!(
"Failed to generate MMR proof: {err}"
)))
})?;
debug_assert!(leaves.len() == 1, "should always be of length 1");
let leaf = leaves
.pop()
.ok_or(sp_blockchain::Error::Application(Box::from(
"Unexpected missing mmr leaf".to_string(),
)))?;

Ok(ConsensusChainMmrLeafProof {
consensus_block_number: prove_at_number,
consensus_block_hash: prove_at_hash,
opaque_mmr_leaf: leaf,
proof,
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit 13a6380

Please sign in to comment.