This repository was archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
pallet-mmr: move offchain logic to client-side gadget #12753
Merged
Merged
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
31f2bf8
Move MMR utils methods from pallet to primitives
serban300 ad18dc2
Add method to MmrApi
serban300 ccb71cd
Move forks expanding logic from babe to primitives
serban300 67ebfde
Implement MMR gadget
serban300 ebb81df
Remove prunning logic from the MMR pallet
serban300 8857248
Code review changes: 1st iteration
serban300 4f56ae6
Replace MaybeCanonEngine with CanonEngineBuilder
serban300 cb6a96f
fix mmr_leaves_count() for kitchen sink demo
serban300 f21593c
Update client/merkle-mountain-range/src/canon_engine.rs
serban300 2cb3b3b
Code review changes: 2nd iteration
serban300 54fa18e
fix INDEXING_PREFIX
serban300 710e698
Merge remote-tracking branch 'upstream/master' into mmr
serban300 b6e733e
Merge branch 'master' of github.com:paritytech/substrate into mmr
acatangiu 4981702
impl review comments
acatangiu e39b651
add documentation and minor rename
acatangiu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| [package] | ||
| name = "mmr-gadget" | ||
| version = "4.0.0-dev" | ||
| authors = ["Parity Technologies <[email protected]>"] | ||
| edition = "2021" | ||
| license = "GPL-3.0-or-later WITH Classpath-exception-2.0" | ||
| repository = "https://github.com/paritytech/substrate" | ||
| description = "MMR Client gadget for substrate" | ||
| homepage = "https://substrate.io" | ||
|
|
||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
|
||
| [dependencies] | ||
| codec = { package = "parity-scale-codec", version = "3.0.0" } | ||
| futures = "0.3" | ||
| log = "0.4" | ||
| beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } | ||
| sc-client-api = { version = "4.0.0-dev", path = "../api" } | ||
| sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } | ||
| sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } | ||
| sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } | ||
| sp-core = { version = "7.0.0", path = "../../primitives/core" } | ||
| sp-io = { version = "7.0.0", path = "../../primitives/io" } | ||
| sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } | ||
| sc-offchain = { version = "4.0.0-dev", path = "../offchain" } | ||
| sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } | ||
|
|
||
| [dev-dependencies] | ||
| tokio = "1.17.0" | ||
| sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } | ||
| substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } | ||
| async-std = { version = "1.11.0", default-features = false } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| // This file is part of Substrate. | ||
|
|
||
| // Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. | ||
| // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 | ||
|
|
||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
|
|
||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| use std::{marker::PhantomData, sync::Arc}; | ||
|
|
||
| use log::{debug, error}; | ||
|
|
||
| use sc_client_api::FinalityNotification; | ||
| use sc_offchain::OffchainDb; | ||
| use sp_blockchain::{ForkBackend, HeaderBackend, HeaderMetadata}; | ||
| use sp_core::offchain::{DbExternalities, OffchainStorage, StorageKind}; | ||
| use sp_mmr_primitives::{utils, utils::NodesUtils, LeafIndex, NodeIndex}; | ||
| use sp_runtime::{ | ||
| traits::{Block, Header, One}, | ||
| Saturating, | ||
| }; | ||
|
|
||
| use crate::LOG_TARGET; | ||
|
|
||
| pub struct CanonEngine<C, B: Block, S> { | ||
| pub client: Arc<C>, | ||
| pub offchain_db: OffchainDb<S>, | ||
| pub indexing_prefix: Vec<u8>, | ||
| pub first_mmr_block: <B::Header as Header>::Number, | ||
|
|
||
| pub _phantom: PhantomData<B>, | ||
| } | ||
|
|
||
| impl<C, S, B> CanonEngine<C, B, S> | ||
| where | ||
| C: HeaderBackend<B> + HeaderMetadata<B>, | ||
| S: OffchainStorage, | ||
| B: Block, | ||
| { | ||
| fn block_number_to_leaf_index( | ||
| &self, | ||
| block_num: <B::Header as Header>::Number, | ||
| ) -> Result<LeafIndex, sp_mmr_primitives::Error> { | ||
| utils::block_num_to_leaf_index::<B::Header>(block_num, self.first_mmr_block).map_err(|e| { | ||
| error!( | ||
| target: LOG_TARGET, | ||
| "Error converting block number {} to leaf index: {:?}", block_num, e | ||
| ); | ||
| e | ||
| }) | ||
| } | ||
|
|
||
| fn node_temp_offchain_key(&self, pos: NodeIndex, parent_hash: &B::Hash) -> Vec<u8> { | ||
| NodesUtils::node_temp_offchain_key::<B::Header>(&self.indexing_prefix, pos, parent_hash) | ||
| } | ||
|
|
||
| fn node_canon_offchain_key(&self, pos: NodeIndex) -> Vec<u8> { | ||
| NodesUtils::node_canon_offchain_key(&self.indexing_prefix, pos) | ||
| } | ||
|
|
||
| fn prune_branch(&mut self, block_hash: &B::Hash) { | ||
| let header = match self.client.header_metadata(*block_hash) { | ||
| Ok(header) => header, | ||
| _ => { | ||
| error!( | ||
| target: LOG_TARGET, | ||
| "Stale block {} not found. Couldn't prune associated stale branch.", block_hash | ||
| ); | ||
| return | ||
| }, | ||
| }; | ||
|
|
||
| // If we can't convert the block number to a leaf index, the chain state is probably | ||
| // corrupted. We only log the error, hoping that the chain state will be fixed. | ||
| let stale_leaf = match self.block_number_to_leaf_index(header.number) { | ||
| Ok(stale_leaf) => stale_leaf, | ||
| Err(_) => return, | ||
| }; | ||
| // We prune the leaf associated with the provided block and all the nodes added by that | ||
| // leaf. | ||
| let stale_nodes = NodesUtils::right_branch_ending_in_leaf(stale_leaf); | ||
| debug!( | ||
| target: LOG_TARGET, | ||
| "Nodes to prune for stale block {}: {:?}", header.number, stale_nodes | ||
| ); | ||
|
|
||
| for pos in stale_nodes { | ||
| let temp_key = self.node_temp_offchain_key(pos, &header.parent); | ||
| self.offchain_db.local_storage_clear(StorageKind::PERSISTENT, &temp_key); | ||
| debug!(target: LOG_TARGET, "Pruned elem at pos {} with temp key {:?}", pos, temp_key); | ||
| } | ||
| } | ||
|
|
||
| fn canonicalize_branch( | ||
| &mut self, | ||
| block_num: <B::Header as Header>::Number, | ||
| parent_hash: &B::Hash, | ||
| ) { | ||
| // Don't canonicalize branches corresponding to blocks for which the MMR pallet | ||
| // wasn't yet initialized. | ||
| if block_num < self.first_mmr_block { | ||
| return | ||
| } | ||
|
|
||
| // If we can't convert the block number to a leaf index, the chain state is probably | ||
| // corrupted. We only log the error, hoping that the chain state will be fixed. | ||
| let to_canon_leaf = match self.block_number_to_leaf_index(block_num) { | ||
| Ok(to_canon_leaf) => to_canon_leaf, | ||
| Err(_) => return, | ||
| }; | ||
| // We "canonicalize" the leaf associated with the provided block | ||
| // and all the nodes added by that leaf. | ||
| let to_canon_nodes = NodesUtils::right_branch_ending_in_leaf(to_canon_leaf); | ||
| debug!( | ||
| target: LOG_TARGET, | ||
| "Nodes to canonicalize for block {}: {:?}", block_num, to_canon_nodes | ||
| ); | ||
|
|
||
| for pos in to_canon_nodes { | ||
| let temp_key = self.node_temp_offchain_key(pos, parent_hash); | ||
| if let Some(elem) = | ||
| self.offchain_db.local_storage_get(StorageKind::PERSISTENT, &temp_key) | ||
| { | ||
| let canon_key = self.node_canon_offchain_key(pos); | ||
| self.offchain_db.local_storage_set(StorageKind::PERSISTENT, &canon_key, &elem); | ||
| self.offchain_db.local_storage_clear(StorageKind::PERSISTENT, &temp_key); | ||
| debug!( | ||
| target: LOG_TARGET, | ||
| "Moved elem at pos {} from temp key {:?} to canon key {:?}", | ||
| pos, | ||
| temp_key, | ||
| canon_key | ||
| ); | ||
| } else { | ||
| error!( | ||
| target: LOG_TARGET, | ||
| "Couldn't canonicalize elem at pos {} using temp key {:?}", pos, temp_key | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Move leafs and nodes added by finalized blocks in offchain db from _fork-aware key_ to | ||
| /// _canonical key_. | ||
| /// Prune leafs and nodes added by stale blocks in offchain db from _fork-aware key_. | ||
| pub fn canonicalize_and_prune( | ||
| &mut self, | ||
| best_finalized: &(<B::Header as Header>::Number, B::Hash), | ||
| notification: &FinalityNotification<B>, | ||
| ) { | ||
| // Move offchain MMR nodes for finalized blocks to canonical keys. | ||
| let (mut parent_number, mut parent_hash) = *best_finalized; | ||
| for block_hash in notification | ||
| .tree_route | ||
| .iter() | ||
| .cloned() | ||
| .chain(std::iter::once(notification.hash)) | ||
| { | ||
| let block_number = parent_number.saturating_add(One::one()); | ||
| self.canonicalize_branch(block_number, &parent_hash); | ||
|
|
||
| (parent_number, parent_hash) = (block_number, block_hash); | ||
| } | ||
|
|
||
| // Remove offchain MMR nodes for stale forks. | ||
| let stale_forks = self.client.expand_forks(¬ification.stale_heads); | ||
| for hash in stale_forks.iter() { | ||
| self.prune_branch(hash); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::test_utils::run_test_with_mmr_gadget; | ||
| use sp_runtime::generic::BlockId; | ||
| use std::time::Duration; | ||
|
|
||
| #[test] | ||
| fn canonicalize_and_prune_works_correctly() { | ||
| run_test_with_mmr_gadget(|client| async move { | ||
| // -> D4 -> D5 | ||
| // G -> A1 -> A2 -> A3 -> A4 | ||
| // -> B1 -> B2 -> B3 | ||
| // -> C1 | ||
|
|
||
| let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; | ||
| let a2 = client.import_block(&BlockId::Hash(a1.hash()), b"a2", Some(1)).await; | ||
| let a3 = client.import_block(&BlockId::Hash(a2.hash()), b"a3", Some(2)).await; | ||
| let a4 = client.import_block(&BlockId::Hash(a3.hash()), b"a4", Some(3)).await; | ||
|
|
||
| let b1 = client.import_block(&BlockId::Number(0), b"b1", Some(0)).await; | ||
| let b2 = client.import_block(&BlockId::Hash(b1.hash()), b"b2", Some(1)).await; | ||
| let b3 = client.import_block(&BlockId::Hash(b2.hash()), b"b3", Some(2)).await; | ||
|
|
||
| let c1 = client.import_block(&BlockId::Number(0), b"c1", Some(0)).await; | ||
|
|
||
| let d4 = client.import_block(&BlockId::Hash(a3.hash()), b"d4", Some(3)).await; | ||
| let d5 = client.import_block(&BlockId::Hash(d4.hash()), b"d5", Some(4)).await; | ||
|
|
||
| client.finalize_block(a3.hash(), Some(3)); | ||
| async_std::task::sleep(Duration::from_millis(200)).await; | ||
| // expected finalized heads: a1, a2, a3 | ||
| client.assert_canonicalized(&[&a1, &a2, &a3]); | ||
| // expected stale heads: c1 | ||
| // expected pruned heads because of temp key collision: b1 | ||
| client.assert_pruned(&[&c1, &b1]); | ||
|
|
||
| client.finalize_block(d5.hash(), None); | ||
| async_std::task::sleep(Duration::from_millis(200)).await; | ||
| // expected finalized heads: d4, d5, | ||
| client.assert_canonicalized(&[&d4, &d5]); | ||
| // expected stale heads: b1, b2, b3, a4 | ||
| client.assert_pruned(&[&b1, &b2, &b3, &a4]); | ||
| }) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.