Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

BEEFY: generate historical proofs #11230

Closed
wants to merge 3 commits into from
Closed
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
8 changes: 8 additions & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,14 @@ impl_runtime_apis! {
.map(|(leaf, proof)| (mmr::EncodableOpaqueLeaf::from_leaf(&leaf), proof))
}

fn generate_historical_proof(
leaf_index: pallet_mmr::primitives::LeafIndex,
leaves_count: pallet_mmr::primitives::LeafIndex,
) -> Result<(mmr::EncodableOpaqueLeaf, mmr::Proof<mmr::Hash>), mmr::Error> {
Mmr::generate_historical_proof(leaf_index, leaves_count)
.map(|(leaf, proof)| (mmr::EncodableOpaqueLeaf::from_leaf(&leaf), proof))
}

fn verify_proof(leaf: mmr::EncodableOpaqueLeaf, proof: mmr::Proof<mmr::Hash>)
-> Result<(), mmr::Error>
{
Expand Down
5 changes: 5 additions & 0 deletions client/beefy/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ macro_rules! create_test_api {
unimplemented!()
}

fn generate_historical_proof(_leaf_index: LeafIndex, _leaves_count: LeafIndex)
-> Result<(EncodableOpaqueLeaf, Proof<MmrRootHash>), MmrError> {
unimplemented!()
}

fn verify_proof(_leaf: EncodableOpaqueLeaf, _proof: Proof<MmrRootHash>)
-> Result<(), MmrError> {
unimplemented!()
Expand Down
47 changes: 47 additions & 0 deletions frame/merkle-mountain-range/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ pub trait MmrApi<BlockHash> {
leaf_index: LeafIndex,
at: Option<BlockHash>,
) -> Result<LeafProof<BlockHash>>;

/// Generate MMR proof for given leaf index.
///
/// This method calls into a runtime with MMR pallet included and attempts to generate
/// MMR proof for leaf at given `leaf_index` with MMR fixed to the state with exactly
/// `leaves_count` leaves. `leaves_count` must be larger than the `leaf_index` for
/// function to succeed.
///
/// Optionally, a block hash at which the runtime should be queried can be specified.
/// Note that specifying the block hash isn't super-useful here, unless you're generating
/// proof using non-finalized blocks where there are several competing forks. That's because
/// MMR state will be fixed to the state with `leaves_count`, which already points to some
/// historical block.
///
/// Returns the (full) leaf itself and a proof for this leaf (compact encoding, i.e. hash of
/// the leaf). Both parameters are SCALE-encoded.
#[rpc(name = "mmr_generateHistoricalProof")]
fn generate_historical_proof(
&self,
leaf_index: LeafIndex,
leaves_index: LeafIndex,
at: Option<BlockHash>,
) -> Result<LeafProof<BlockHash>>;
}

/// An implementation of MMR specific RPC methods.
Expand Down Expand Up @@ -117,6 +140,30 @@ where

Ok(LeafProof::new(block_hash, leaf, proof))
}

fn generate_historical_proof(
&self,
leaf_index: LeafIndex,
leaves_count: LeafIndex,
at: Option<<Block as BlockT>::Hash>,
) -> Result<LeafProof<<Block as BlockT>::Hash>> {
let api = self.client.runtime_api();
let block_hash = at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash);

let (leaf, proof) = api
.generate_historical_proof_with_context(
&BlockId::hash(block_hash),
sp_core::ExecutionContext::OffchainCall(None),
leaf_index,
leaves_count,
)
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;

Ok(LeafProof::new(block_hash, leaf, proof))
}
}

const RUNTIME_ERROR: i64 = 8000;
Expand Down
18 changes: 17 additions & 1 deletion frame/merkle-mountain-range/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,23 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn generate_proof(
leaf_index: LeafIndex,
) -> Result<(LeafOf<T, I>, primitives::Proof<<T as Config<I>>::Hash>), primitives::Error> {
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(Self::mmr_leaves());
Self::generate_historical_proof(leaf_index, Self::mmr_leaves())
}

/// Generate a MMR proof for the given `leaf_index`.
///
/// This method is not touching any runtime storage keys, as all it needs is coming
/// either from arguments, or from the offchain storage.
///
/// Note this method can only be used from an off-chain context
/// (Offchain Worker or Runtime API call), since it requires
/// all the leaves to be present.
/// It may return an error or panic if used incorrectly.
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO it makes sense to add an explicit error in case leaves_count > Self::mmr_leaves()

pub fn generate_historical_proof(
leaf_index: LeafIndex,
leaves_count: LeafIndex,
) -> Result<(LeafOf<T, I>, primitives::Proof<<T as Config<I>>::Hash>), primitives::Error> {
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(leaves_count);
mmr.generate_proof(leaf_index)
}

Expand Down
98 changes: 98 additions & 0 deletions frame/merkle-mountain-range/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,101 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
assert_eq!(crate::Pallet::<Test>::verify_leaf(leaf, proof5), Ok(()));
});
}

#[test]
fn able_to_generate_historical_proofs() {
let _ = env_logger::try_init();
let mut ext = new_test_ext();

// Given full client (where the code is executed) that has 7 blocks (aka 7 MMR leafs).
// Let's imagine that the state for blocks 1..=5 has been pruned already.
//
// And BEEFY light client, which has synced to the 3rd block and needs to verify some
// MMR proof there. It can't just use `generate_proof(block_3_hash)`, because the state
// has already been discarded. So it must use `generate_historical_proof` instead.
let mmr_root_known_to_light_client = ext.execute_with(|| {
new_block(); // 1
new_block(); // 2
new_block(); // 3
let mmr_root_at_3 = crate::RootHash::<Test>::get();
new_block(); // 4
new_block(); // 5
new_block(); // 6
new_block(); // 7
mmr_root_at_3
});
ext.persist_offchain_overlay();

// Try to generate historical proof using latest state. This requires the offchain
// extensions to be present to retrieve full leaf data.
register_offchain_ext(&mut ext);
ext.execute_with(|| {
// ensure that there are exactly 7 leaves
assert_eq!(crate::NumberOfLeaves::<Test>::get(), 7);

// try to generate both regular and historical proofs
let leaf_index = 2;
let leaf_count_at_block_3 = 3;
let (_, regular_proof) = crate::Pallet::<Test>::generate_proof(leaf_index).unwrap();
let (leaf, historical_proof) =
crate::Pallet::<Test>::generate_historical_proof(leaf_index, leaf_count_at_block_3)
.unwrap();

// verify that the regular proof has 3 items:
//
// D
// / \
// / \
// A B C
// / \ / \ / \
// 1 2 3 4 5 6 7
//
// we're proving 3 => we need { 4, A, C++7 }
assert_eq!(regular_proof.items.len(), 3);

// verify that the historical proof has 1 item:
//
// A
// / \
// 1 2 3
//
// we're proving 3 => we need { A }
assert_eq!(historical_proof.items.len(), 1);

// verify that the light client is able to verify historical proof using MMR root from block
// 3
let verification_result = verify_leaf_proof::<HashingOf<Test, ()>, LeafOf<Test, ()>>(
mmr_root_known_to_light_client,
leaf.into(),
historical_proof,
);
assert_eq!(verification_result, Ok(()));
});
}

#[test]
fn does_not_panic_when_generating_historical_proofs() {
let _ = env_logger::try_init();
let mut ext = new_test_ext();

// given 7 blocks (7 MMR leaves)
ext.execute_with(|| init_chain(7));
ext.persist_offchain_overlay();

// Try to generate historical proof with invalid arguments. This requires the offchain
// extensions to be present to retrieve full leaf data.
register_offchain_ext(&mut ext);
ext.execute_with(|| {
// when leaf index is invalid
assert_eq!(
crate::Pallet::<Test>::generate_historical_proof(10, 7),
Err(Error::LeafNotFound),
);

// when leaves count is invalid
assert_eq!(
crate::Pallet::<Test>::generate_historical_proof(3, 100),
Err(Error::GenerateProof),
);
});
}
11 changes: 11 additions & 0 deletions primitives/merkle-mountain-range/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,17 @@ sp_api::decl_runtime_apis! {
/// Generate MMR proof for a leaf under given index.
fn generate_proof(leaf_index: LeafIndex) -> Result<(EncodableOpaqueLeaf, Proof<Hash>), Error>;

/// Generate a historical MMR proof for a leaf with given index.
///
/// This function may be used to generate proofs using MMR state at some earlier
/// point. To do that, you shall provide historical value of the `leaves_count`.
/// The proof generated by this function may be verified by clients that only have
/// some old MMR root (corresponding to the `leaves_count`).
fn generate_historical_proof(
leaf_index: LeafIndex,
leaves_count: LeafIndex,
) -> Result<(EncodableOpaqueLeaf, Proof<Hash>), Error>;
Comment on lines +408 to +411
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to directly add this with support for multiple leaves at once? (see #10635)

Would the light client benefit for generating historical proofs in a batch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah - I think it makes sense to add support for proving several leaves, even though we'll only be using this API to prove single leaf now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then let's probably defer this PR until #10635 is merged to reuse primitives from there && etc


/// Verify MMR proof against on-chain MMR.
///
/// Note this function will use on-chain MMR root hash and check if the proof
Expand Down