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

Stateless mmr proof for the consensus chain #2687

Merged
merged 8 commits into from
Apr 19, 2024
Merged
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);
Copy link
Member

Choose a reason for hiding this comment

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

maybe we should remove this per our discussion since this is unneeded at this time
cc: @nazar-pc

Copy link
Member

Choose a reason for hiding this comment

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

Note that in case we already deployed runtime with it (IDK) removing corresponding enum variant will be a breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe this is already deployed to 3h so will leave a TODO for now.


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.
Copy link
Member

Choose a reason for hiding this comment

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

Please send a PR upstream right away in such cases: paritytech/polkadot-sdk#5442

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>(
Copy link
Member

Choose a reason for hiding this comment

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

will we be using this in the relayer while generating MMR prrofs for XDM ?

Copy link
Member Author

@NingLin-P NingLin-P Apr 18, 2024

Choose a reason for hiding this comment

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

We can use this for generating MMR proofs for the consensus XDM, but there are some slight differences between the proof of the consensus chain and the domain chain, so we still need to keep 2 versions of this anyway, I just distinguish them as XDM vs FP instead of consensus vs domain.

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
Loading