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
Mmr persist state #12822
Merged
Merged
Mmr persist state #12822
Changes from 7 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
dd2e956
client/mmr: on init do canonicalization catch-up
acatangiu 84fb595
client/mmr: add more tests
acatangiu 7f47d9b
client/mmr: persist gadget progress in aux db
acatangiu 127c921
client/mmr: add more tests
acatangiu 958ebbd
client/mmr: replace async_std with tokio
acatangiu d43fd61
remove leftover comment
acatangiu 213cc8f
Merge branch 'master' of github.com:paritytech/substrate into mmr-per…
acatangiu 62fca15
Merge branch 'master' of github.com:paritytech/substrate into mmr-per…
acatangiu 6d16249
address review comments
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,228 @@ | ||
| // This file is part of Substrate. | ||
|
|
||
| // Copyright (C) 2019-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/>. | ||
|
|
||
| //! Schema for MMR-gadget state persisted in the aux-db. | ||
| use crate::LOG_TARGET; | ||
| use codec::{Decode, Encode}; | ||
| use log::{info, trace}; | ||
| use sc_client_api::backend::AuxStore; | ||
| use sp_blockchain::{Error as ClientError, Result as ClientResult}; | ||
| use sp_runtime::traits::{Block, NumberFor}; | ||
|
|
||
| const VERSION_KEY: &[u8] = b"mmr_auxschema_version"; | ||
| const GADGET_STATE: &[u8] = b"mmr_gadget_state"; | ||
|
|
||
| const CURRENT_VERSION: u32 = 1; | ||
| pub(crate) type PersistedState<B> = NumberFor<B>; | ||
|
|
||
| pub(crate) fn write_current_version<B: AuxStore>(backend: &B) -> ClientResult<()> { | ||
| info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); | ||
acatangiu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) | ||
| } | ||
|
|
||
| /// Write gadget state. | ||
| pub(crate) fn write_gadget_state<B: Block, BE: AuxStore>( | ||
| backend: &BE, | ||
| state: &PersistedState<B>, | ||
| ) -> ClientResult<()> { | ||
| trace!(target: LOG_TARGET, "🥩 persisting {:?}", state); | ||
| backend.insert_aux(&[(GADGET_STATE, state.encode().as_slice())], &[]) | ||
| } | ||
|
|
||
| fn load_decode<B: AuxStore, T: Decode>(backend: &B, key: &[u8]) -> ClientResult<Option<T>> { | ||
| match backend.get_aux(key)? { | ||
| None => Ok(None), | ||
| Some(t) => T::decode(&mut &t[..]) | ||
| .map_err(|e| ClientError::Backend(format!("MMR aux DB is corrupted: {}", e))) | ||
| .map(Some), | ||
| } | ||
| } | ||
|
|
||
| /// Load or initialize persistent data from backend. | ||
| pub(crate) fn load_persistent<B, BE>(backend: &BE) -> ClientResult<Option<PersistedState<B>>> | ||
| where | ||
| B: Block, | ||
| BE: AuxStore, | ||
| { | ||
| let version: Option<u32> = load_decode(backend, VERSION_KEY)?; | ||
|
|
||
| match version { | ||
| None => (), | ||
| Some(1) => return load_decode::<_, PersistedState<B>>(backend, GADGET_STATE), | ||
| other => | ||
| return Err(ClientError::Backend(format!("Unsupported MMR aux DB version: {:?}", other))), | ||
| } | ||
|
|
||
| // No persistent state found in DB. | ||
| Ok(None) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| pub(crate) mod tests { | ||
| use super::*; | ||
| use crate::test_utils::{ | ||
| run_test_with_mmr_gadget_pre_post_using_client, MmrBlock, MockClient, OffchainKeyType, | ||
| }; | ||
| use parking_lot::Mutex; | ||
| use sp_core::offchain::{DbExternalities, StorageKind}; | ||
| use sp_mmr_primitives::utils::NodesUtils; | ||
| use sp_runtime::generic::BlockId; | ||
| use std::{sync::Arc, time::Duration}; | ||
| use substrate_test_runtime_client::{runtime::Block, Backend}; | ||
|
|
||
| #[test] | ||
| fn should_load_persistent_sanity_checks() { | ||
| let client = MockClient::new(); | ||
| let backend = &*client.backend; | ||
|
|
||
| // version not available in db -> None | ||
| assert_eq!(load_persistent::<Block, Backend>(backend).unwrap(), None); | ||
|
|
||
| // populate version in db | ||
| write_current_version(backend).unwrap(); | ||
| // verify correct version is retrieved | ||
| assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); | ||
|
|
||
| // version is available in db but state isn't -> None | ||
| assert_eq!(load_persistent::<Block, Backend>(backend).unwrap(), None); | ||
| } | ||
|
|
||
| #[test] | ||
| fn should_persist_progress_across_runs() { | ||
| sp_tracing::try_init_simple(); | ||
|
|
||
| let client = Arc::new(MockClient::new()); | ||
| let backend = client.backend.clone(); | ||
|
|
||
| // version not available in db -> None | ||
| assert_eq!(load_decode::<Backend, Option<u32>>(&*backend, VERSION_KEY).unwrap(), None); | ||
| // state not available in db -> None | ||
| assert_eq!(load_persistent::<Block, Backend>(&*backend).unwrap(), None); | ||
| // run the gadget while importing and finalizing 3 blocks | ||
| run_test_with_mmr_gadget_pre_post_using_client( | ||
| client.clone(), | ||
| |_| async {}, | ||
| |client| async move { | ||
| let a1 = client.import_block(&BlockId::Number(0), b"a1", Some(0)).await; | ||
| let a2 = client.import_block(&BlockId::Number(1), b"a2", Some(1)).await; | ||
| let a3 = client.import_block(&BlockId::Number(2), b"a3", Some(2)).await; | ||
| client.finalize_block(a3.hash(), Some(3)); | ||
| tokio::time::sleep(Duration::from_millis(200)).await; | ||
| // a1, a2, a3 were canonicalized | ||
| client.assert_canonicalized(&[&a1, &a2, &a3]); | ||
| }, | ||
| ); | ||
|
|
||
| // verify previous progress was persisted and run the gadget again | ||
| run_test_with_mmr_gadget_pre_post_using_client( | ||
| client.clone(), | ||
| |client| async move { | ||
| let backend = &*client.backend; | ||
| // check there is both version and best canon available in db before running gadget | ||
| assert_eq!(load_decode(backend, VERSION_KEY).unwrap(), Some(CURRENT_VERSION)); | ||
| assert_eq!(load_persistent::<Block, Backend>(backend).unwrap(), Some(3)); | ||
| }, | ||
| |client| async move { | ||
| let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; | ||
| let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; | ||
| let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; | ||
| client.finalize_block(a6.hash(), Some(6)); | ||
| tokio::time::sleep(Duration::from_millis(200)).await; | ||
|
|
||
| // a4, a5, a6 were canonicalized | ||
| client.assert_canonicalized(&[&a4, &a5, &a6]); | ||
| // check persisted best canon was updated | ||
| assert_eq!(load_persistent::<Block, Backend>(&*client.backend).unwrap(), Some(6)); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn should_resume_from_persisted_state() { | ||
| sp_tracing::try_init_simple(); | ||
|
|
||
| let client = Arc::new(MockClient::new()); | ||
| let blocks = Arc::new(Mutex::new(Vec::<MmrBlock>::new())); | ||
| let blocks_clone = blocks.clone(); | ||
|
|
||
| // run the gadget while importing and finalizing 3 blocks | ||
| run_test_with_mmr_gadget_pre_post_using_client( | ||
| client.clone(), | ||
| |_| async {}, | ||
| |client| async move { | ||
| let mut blocks = blocks_clone.lock(); | ||
| blocks.push(client.import_block(&BlockId::Number(0), b"a1", Some(0)).await); | ||
| blocks.push(client.import_block(&BlockId::Number(1), b"a2", Some(1)).await); | ||
| blocks.push(client.import_block(&BlockId::Number(2), b"a3", Some(2)).await); | ||
| client.finalize_block(blocks.last().unwrap().hash(), Some(3)); | ||
| tokio::time::sleep(Duration::from_millis(200)).await; | ||
| // a1, a2, a3 were canonicalized | ||
| let slice: Vec<&MmrBlock> = blocks.iter().collect(); | ||
| client.assert_canonicalized(&slice); | ||
|
|
||
| // now manually move them back to non-canon/temp location | ||
| let mut offchain_db = client.offchain_db(); | ||
| for mmr_block in slice { | ||
| for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) | ||
| { | ||
| let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon); | ||
| let val = offchain_db | ||
| .local_storage_get(StorageKind::PERSISTENT, &canon_key) | ||
| .unwrap(); | ||
| offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key); | ||
|
|
||
| let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp); | ||
| offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val); | ||
| } | ||
| } | ||
| }, | ||
| ); | ||
|
|
||
| let blocks_clone = blocks.clone(); | ||
| // verify new gadget continues from block 4 and ignores 1, 2, 3 based on persisted state | ||
| run_test_with_mmr_gadget_pre_post_using_client( | ||
| client.clone(), | ||
| |client| async move { | ||
| let blocks = blocks_clone.lock(); | ||
| let slice: Vec<&MmrBlock> = blocks.iter().collect(); | ||
|
|
||
| // verify persisted state says a1, a2, a3 were canonicalized, | ||
| assert_eq!(load_persistent::<Block, Backend>(&*client.backend).unwrap(), Some(3)); | ||
| // but actually they are NOT canon (we manually reverted them earlier). | ||
| client.assert_not_canonicalized(&slice); | ||
| }, | ||
| |client| async move { | ||
| let a4 = client.import_block(&BlockId::Number(3), b"a4", Some(3)).await; | ||
| let a5 = client.import_block(&BlockId::Number(4), b"a5", Some(4)).await; | ||
| let a6 = client.import_block(&BlockId::Number(5), b"a6", Some(5)).await; | ||
| client.finalize_block(a6.hash(), Some(6)); | ||
| tokio::time::sleep(Duration::from_millis(200)).await; | ||
|
|
||
| let block_1_to_3 = blocks.lock(); | ||
| let slice: Vec<&MmrBlock> = block_1_to_3.iter().collect(); | ||
| // verify a1, a2, a3 are still NOT canon (skipped by gadget based on data in aux db) | ||
| client.assert_not_canonicalized(&slice); | ||
| // but a4, a5, a6 were canonicalized | ||
| client.assert_canonicalized(&[&a4, &a5, &a6]); | ||
| // check persisted best canon was updated | ||
| assert_eq!(load_persistent::<Block, Backend>(&*client.backend).unwrap(), Some(6)); | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
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
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.