mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-15 18:31:05 +00:00
Add missing child trie rpc on_custom_message handler (#3522)
* missing on_custom for child read. * fix and complete, add test cases. * shorten line. * replace vecs of key value tuple with maps.
This commit is contained in:
@@ -48,7 +48,7 @@ mod tests {
|
|||||||
runtime::{Hash, Transfer, Block, BlockNumber, Header, Digest},
|
runtime::{Hash, Transfer, Block, BlockNumber, Header, Digest},
|
||||||
AccountKeyring, Sr25519Keyring,
|
AccountKeyring, Sr25519Keyring,
|
||||||
};
|
};
|
||||||
use primitives::Blake2Hasher;
|
use primitives::{Blake2Hasher, map};
|
||||||
use hex::*;
|
use hex::*;
|
||||||
|
|
||||||
native_executor_instance!(
|
native_executor_instance!(
|
||||||
@@ -152,7 +152,8 @@ mod tests {
|
|||||||
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
|
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
vec![],
|
map![],
|
||||||
|
map![],
|
||||||
).genesis_map();
|
).genesis_map();
|
||||||
let genesis_hash = insert_genesis_block(&mut storage);
|
let genesis_hash = insert_genesis_block(&mut storage);
|
||||||
|
|
||||||
@@ -181,7 +182,8 @@ mod tests {
|
|||||||
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
|
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
|
||||||
1000,
|
1000,
|
||||||
None,
|
None,
|
||||||
vec![],
|
map![],
|
||||||
|
map![],
|
||||||
).genesis_map();
|
).genesis_map();
|
||||||
let genesis_hash = insert_genesis_block(&mut storage);
|
let genesis_hash = insert_genesis_block(&mut storage);
|
||||||
|
|
||||||
@@ -210,7 +212,8 @@ mod tests {
|
|||||||
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
|
vec![AccountKeyring::One.into(), AccountKeyring::Two.into()],
|
||||||
68,
|
68,
|
||||||
None,
|
None,
|
||||||
vec![],
|
map![],
|
||||||
|
map![],
|
||||||
).genesis_map();
|
).genesis_map();
|
||||||
let genesis_hash = insert_genesis_block(&mut storage);
|
let genesis_hash = insert_genesis_block(&mut storage);
|
||||||
|
|
||||||
|
|||||||
@@ -570,7 +570,8 @@ pub mod tests {
|
|||||||
let remote_block_id = BlockId::Number(0);
|
let remote_block_id = BlockId::Number(0);
|
||||||
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
|
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
|
||||||
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
|
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
|
||||||
remote_block_header.state_root = remote_client.state_at(&remote_block_id).unwrap().storage_root(::std::iter::empty()).0.into();
|
remote_block_header.state_root = remote_client.state_at(&remote_block_id).unwrap()
|
||||||
|
.storage_root(::std::iter::empty()).0.into();
|
||||||
|
|
||||||
// 'fetch' read proof from remote node
|
// 'fetch' read proof from remote node
|
||||||
let heap_pages = remote_client.storage(&remote_block_id, &StorageKey(well_known_keys::HEAP_PAGES.to_vec()))
|
let heap_pages = remote_client.storage(&remote_block_id, &StorageKey(well_known_keys::HEAP_PAGES.to_vec()))
|
||||||
@@ -592,6 +593,46 @@ pub mod tests {
|
|||||||
(local_checker, remote_block_header, remote_read_proof, heap_pages)
|
(local_checker, remote_block_header, remote_read_proof, heap_pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_for_read_child_proof_check() -> (TestChecker, Header, Vec<Vec<u8>>, Vec<u8>) {
|
||||||
|
use test_client::DefaultTestClientBuilderExt;
|
||||||
|
use test_client::TestClientBuilderExt;
|
||||||
|
// prepare remote client
|
||||||
|
let remote_client = test_client::TestClientBuilder::new()
|
||||||
|
.add_extra_child_storage(b":child_storage:default:child1".to_vec(), b"key1".to_vec(), b"value1".to_vec())
|
||||||
|
.build();
|
||||||
|
let remote_block_id = BlockId::Number(0);
|
||||||
|
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
|
||||||
|
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
|
||||||
|
remote_block_header.state_root = remote_client.state_at(&remote_block_id).unwrap()
|
||||||
|
.storage_root(::std::iter::empty()).0.into();
|
||||||
|
|
||||||
|
// 'fetch' child read proof from remote node
|
||||||
|
let child_value = remote_client.child_storage(
|
||||||
|
&remote_block_id,
|
||||||
|
&StorageKey(b":child_storage:default:child1".to_vec()),
|
||||||
|
&StorageKey(b"key1".to_vec()),
|
||||||
|
).unwrap().unwrap().0;
|
||||||
|
assert_eq!(b"value1"[..], child_value[..]);
|
||||||
|
let remote_read_proof = remote_client.read_child_proof(
|
||||||
|
&remote_block_id,
|
||||||
|
b":child_storage:default:child1",
|
||||||
|
b"key1",
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// check locally
|
||||||
|
let local_storage = InMemoryBlockchain::<Block>::new();
|
||||||
|
local_storage.insert(
|
||||||
|
remote_block_hash,
|
||||||
|
remote_block_header.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
crate::backend::NewBlockState::Final,
|
||||||
|
).unwrap();
|
||||||
|
let local_executor = NativeExecutor::<test_client::LocalExecutor>::new(None);
|
||||||
|
let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor);
|
||||||
|
(local_checker, remote_block_header, remote_read_proof, child_value)
|
||||||
|
}
|
||||||
|
|
||||||
fn prepare_for_header_proof_check(insert_cht: bool) -> (TestChecker, Hash, Header, Vec<Vec<u8>>) {
|
fn prepare_for_header_proof_check(insert_cht: bool) -> (TestChecker, Hash, Header, Vec<Vec<u8>>) {
|
||||||
// prepare remote client
|
// prepare remote client
|
||||||
let remote_client = test_client::new();
|
let remote_client = test_client::new();
|
||||||
@@ -638,6 +679,26 @@ pub mod tests {
|
|||||||
}, remote_read_proof).unwrap().unwrap()[0], heap_pages as u8);
|
}, remote_read_proof).unwrap().unwrap()[0], heap_pages as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn storage_child_read_proof_is_generated_and_checked() {
|
||||||
|
let (
|
||||||
|
local_checker,
|
||||||
|
remote_block_header,
|
||||||
|
remote_read_proof,
|
||||||
|
result,
|
||||||
|
) = prepare_for_read_child_proof_check();
|
||||||
|
assert_eq!((&local_checker as &dyn FetchChecker<Block>).check_read_child_proof(
|
||||||
|
&RemoteReadChildRequest::<Header> {
|
||||||
|
block: remote_block_header.hash(),
|
||||||
|
header: remote_block_header,
|
||||||
|
storage_key: b":child_storage:default:child1".to_vec(),
|
||||||
|
key: b"key1".to_vec(),
|
||||||
|
retry_count: None,
|
||||||
|
},
|
||||||
|
remote_read_proof
|
||||||
|
).unwrap().unwrap(), result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn header_proof_is_generated_and_checked() {
|
fn header_proof_is_generated_and_checked() {
|
||||||
let (local_checker, local_cht_root, remote_block_header, remote_header_proof) = prepare_for_header_proof_check(true);
|
let (local_checker, local_cht_root, remote_block_header, remote_header_proof) = prepare_for_header_proof_check(true);
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ pub trait Client<Block: BlockT>: Send + Sync {
|
|||||||
/// Get storage read execution proof.
|
/// Get storage read execution proof.
|
||||||
fn read_proof(&self, block: &Block::Hash, key: &[u8]) -> Result<Vec<Vec<u8>>, Error>;
|
fn read_proof(&self, block: &Block::Hash, key: &[u8]) -> Result<Vec<Vec<u8>>, Error>;
|
||||||
|
|
||||||
|
/// Get child storage read execution proof.
|
||||||
|
fn read_child_proof(&self, block: &Block::Hash, storage_key: &[u8], key: &[u8]) -> Result<Vec<Vec<u8>>, Error>;
|
||||||
|
|
||||||
/// Get method execution proof.
|
/// Get method execution proof.
|
||||||
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error>;
|
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error>;
|
||||||
|
|
||||||
@@ -113,6 +116,16 @@ impl<B, E, Block, RA> Client<Block> for SubstrateClient<B, E, Block, RA> where
|
|||||||
(self as &SubstrateClient<B, E, Block, RA>).read_proof(&BlockId::Hash(block.clone()), key)
|
(self as &SubstrateClient<B, E, Block, RA>).read_proof(&BlockId::Hash(block.clone()), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_child_proof(
|
||||||
|
&self,
|
||||||
|
block: &Block::Hash,
|
||||||
|
storage_key: &[u8],
|
||||||
|
key: &[u8]
|
||||||
|
) -> Result<Vec<Vec<u8>>, Error> {
|
||||||
|
(self as &SubstrateClient<B, E, Block, RA>)
|
||||||
|
.read_child_proof(&BlockId::Hash(block.clone()), storage_key, key)
|
||||||
|
}
|
||||||
|
|
||||||
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error> {
|
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error> {
|
||||||
(self as &SubstrateClient<B, E, Block, RA>).execution_proof(&BlockId::Hash(block.clone()), method, data)
|
(self as &SubstrateClient<B, E, Block, RA>).execution_proof(&BlockId::Hash(block.clone()), method, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,7 +547,8 @@ impl<B: BlockT, S: NetworkSpecialization<B>, H: ExHashT> Protocol<B, S, H> {
|
|||||||
self.on_finality_proof_request(who, request),
|
self.on_finality_proof_request(who, request),
|
||||||
GenericMessage::FinalityProofResponse(response) =>
|
GenericMessage::FinalityProofResponse(response) =>
|
||||||
return self.on_finality_proof_response(who, response),
|
return self.on_finality_proof_response(who, response),
|
||||||
GenericMessage::RemoteReadChildRequest(_) => {}
|
GenericMessage::RemoteReadChildRequest(request) =>
|
||||||
|
self.on_remote_read_child_request(who, request),
|
||||||
GenericMessage::Consensus(msg) => {
|
GenericMessage::Consensus(msg) => {
|
||||||
if self.context_data.peers.get(&who).map_or(false, |peer| peer.info.protocol_version > 2) {
|
if self.context_data.peers.get(&who).map_or(false, |peer| peer.info.protocol_version > 2) {
|
||||||
self.consensus_gossip.on_incoming(
|
self.consensus_gossip.on_incoming(
|
||||||
@@ -1293,6 +1294,36 @@ impl<B: BlockT, S: NetworkSpecialization<B>, H: ExHashT> Protocol<B, S, H> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_remote_read_child_request(
|
||||||
|
&mut self,
|
||||||
|
who: PeerId,
|
||||||
|
request: message::RemoteReadChildRequest<B::Hash>,
|
||||||
|
) {
|
||||||
|
trace!(target: "sync", "Remote read child request {} from {} ({} {} at {})",
|
||||||
|
request.id, who, request.storage_key.to_hex::<String>(), request.key.to_hex::<String>(), request.block);
|
||||||
|
let proof = match self.context_data.chain.read_child_proof(&request.block, &request.storage_key, &request.key) {
|
||||||
|
Ok(proof) => proof,
|
||||||
|
Err(error) => {
|
||||||
|
trace!(target: "sync", "Remote read child request {} from {} ({} {} at {}) failed with: {}",
|
||||||
|
request.id,
|
||||||
|
who,
|
||||||
|
request.storage_key.to_hex::<String>(),
|
||||||
|
request.key.to_hex::<String>(),
|
||||||
|
request.block,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.send_message(
|
||||||
|
who,
|
||||||
|
GenericMessage::RemoteReadResponse(message::RemoteReadResponse {
|
||||||
|
id: request.id,
|
||||||
|
proof,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn on_remote_read_response(
|
fn on_remote_read_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
who: PeerId,
|
who: PeerId,
|
||||||
|
|||||||
@@ -30,14 +30,18 @@ use test_client::{
|
|||||||
fn should_return_storage() {
|
fn should_return_storage() {
|
||||||
const KEY: &[u8] = b":mock";
|
const KEY: &[u8] = b":mock";
|
||||||
const VALUE: &[u8] = b"hello world";
|
const VALUE: &[u8] = b"hello world";
|
||||||
|
const STORAGE_KEY: &[u8] = b":child_storage:default:child";
|
||||||
|
const CHILD_VALUE: &[u8] = b"hello world !";
|
||||||
|
|
||||||
let core = tokio::runtime::Runtime::new().unwrap();
|
let core = tokio::runtime::Runtime::new().unwrap();
|
||||||
let client = TestClientBuilder::new()
|
let client = TestClientBuilder::new()
|
||||||
.add_extra_storage(KEY.to_vec(), VALUE.to_vec())
|
.add_extra_storage(KEY.to_vec(), VALUE.to_vec())
|
||||||
|
.add_extra_child_storage(STORAGE_KEY.to_vec(), KEY.to_vec(), CHILD_VALUE.to_vec())
|
||||||
.build();
|
.build();
|
||||||
let genesis_hash = client.genesis_hash();
|
let genesis_hash = client.genesis_hash();
|
||||||
let client = State::new(Arc::new(client), Subscriptions::new(Arc::new(core.executor())));
|
let client = State::new(Arc::new(client), Subscriptions::new(Arc::new(core.executor())));
|
||||||
let key = StorageKey(KEY.to_vec());
|
let key = StorageKey(KEY.to_vec());
|
||||||
|
let storage_key = StorageKey(STORAGE_KEY.to_vec());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client.storage(key.clone(), Some(genesis_hash).into())
|
client.storage(key.clone(), Some(genesis_hash).into())
|
||||||
@@ -52,6 +56,12 @@ fn should_return_storage() {
|
|||||||
client.storage_size(key.clone(), None).unwrap().unwrap() as usize,
|
client.storage_size(key.clone(), None).unwrap().unwrap() as usize,
|
||||||
VALUE.len(),
|
VALUE.len(),
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client.child_storage(storage_key, key, Some(genesis_hash).into())
|
||||||
|
.map(|x| x.map(|x| x.0.len())).unwrap().unwrap() as usize,
|
||||||
|
CHILD_VALUE.len(),
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub mod trait_tests;
|
|||||||
mod block_builder_ext;
|
mod block_builder_ext;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::collections::HashMap;
|
||||||
pub use block_builder_ext::BlockBuilderExt;
|
pub use block_builder_ext::BlockBuilderExt;
|
||||||
pub use generic_test_client::*;
|
pub use generic_test_client::*;
|
||||||
pub use runtime;
|
pub use runtime;
|
||||||
@@ -97,7 +98,8 @@ pub type LightExecutor = client::light::call_executor::RemoteOrLocalCallExecutor
|
|||||||
pub struct GenesisParameters {
|
pub struct GenesisParameters {
|
||||||
support_changes_trie: bool,
|
support_changes_trie: bool,
|
||||||
heap_pages_override: Option<u64>,
|
heap_pages_override: Option<u64>,
|
||||||
extra_storage: Vec<(Vec<u8>, Vec<u8>)>,
|
extra_storage: HashMap<Vec<u8>, Vec<u8>>,
|
||||||
|
child_extra_storage: HashMap<Vec<u8>, HashMap<Vec<u8>, Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenesisParameters {
|
impl GenesisParameters {
|
||||||
@@ -117,6 +119,7 @@ impl GenesisParameters {
|
|||||||
1000,
|
1000,
|
||||||
self.heap_pages_override,
|
self.heap_pages_override,
|
||||||
self.extra_storage.clone(),
|
self.extra_storage.clone(),
|
||||||
|
self.child_extra_storage.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,6 +187,18 @@ pub trait TestClientBuilderExt<B>: Sized {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the key is empty.
|
/// Panics if the key is empty.
|
||||||
|
fn add_extra_child_storage<SK: Into<Vec<u8>>, K: Into<Vec<u8>>, V: Into<Vec<u8>>>(
|
||||||
|
self,
|
||||||
|
storage_key: SK,
|
||||||
|
key: K,
|
||||||
|
value: V,
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
/// Add an extra child value into the genesis storage.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the key is empty.
|
||||||
fn add_extra_storage<K: Into<Vec<u8>>, V: Into<Vec<u8>>>(self, key: K, value: V) -> Self;
|
fn add_extra_storage<K: Into<Vec<u8>>, V: Into<Vec<u8>>>(self, key: K, value: V) -> Self;
|
||||||
|
|
||||||
/// Build the test client.
|
/// Build the test client.
|
||||||
@@ -214,10 +229,28 @@ impl<B> TestClientBuilderExt<B> for TestClientBuilder<
|
|||||||
fn add_extra_storage<K: Into<Vec<u8>>, V: Into<Vec<u8>>>(mut self, key: K, value: V) -> Self {
|
fn add_extra_storage<K: Into<Vec<u8>>, V: Into<Vec<u8>>>(mut self, key: K, value: V) -> Self {
|
||||||
let key = key.into();
|
let key = key.into();
|
||||||
assert!(!key.is_empty());
|
assert!(!key.is_empty());
|
||||||
self.genesis_init_mut().extra_storage.push((key, value.into()));
|
self.genesis_init_mut().extra_storage.insert(key, value.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_extra_child_storage<SK: Into<Vec<u8>>, K: Into<Vec<u8>>, V: Into<Vec<u8>>>(
|
||||||
|
mut self,
|
||||||
|
storage_key: SK,
|
||||||
|
key: K,
|
||||||
|
value: V,
|
||||||
|
) -> Self {
|
||||||
|
let storage_key = storage_key.into();
|
||||||
|
let key = key.into();
|
||||||
|
assert!(!storage_key.is_empty());
|
||||||
|
assert!(!key.is_empty());
|
||||||
|
self.genesis_init_mut().child_extra_storage
|
||||||
|
.entry(storage_key)
|
||||||
|
.or_insert_with(Default::default)
|
||||||
|
.insert(key, value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn build_with_longest_chain(self) -> (Client<B>, client::LongestChain<B, runtime::Block>) {
|
fn build_with_longest_chain(self) -> (Client<B>, client::LongestChain<B, runtime::Block>) {
|
||||||
self.build_with_native_executor(None)
|
self.build_with_native_executor(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ pub struct GenesisConfig {
|
|||||||
balances: Vec<(AccountId, u64)>,
|
balances: Vec<(AccountId, u64)>,
|
||||||
heap_pages_override: Option<u64>,
|
heap_pages_override: Option<u64>,
|
||||||
/// Additional storage key pairs that will be added to the genesis map.
|
/// Additional storage key pairs that will be added to the genesis map.
|
||||||
extra_storage: Vec<(Vec<u8>, Vec<u8>)>,
|
extra_storage: HashMap<Vec<u8>, Vec<u8>>,
|
||||||
|
child_extra_storage: HashMap<Vec<u8>, HashMap<Vec<u8>, Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenesisConfig {
|
impl GenesisConfig {
|
||||||
@@ -40,7 +41,8 @@ impl GenesisConfig {
|
|||||||
endowed_accounts: Vec<AccountId>,
|
endowed_accounts: Vec<AccountId>,
|
||||||
balance: u64,
|
balance: u64,
|
||||||
heap_pages_override: Option<u64>,
|
heap_pages_override: Option<u64>,
|
||||||
extra_storage: Vec<(Vec<u8>, Vec<u8>)>,
|
extra_storage: HashMap<Vec<u8>, Vec<u8>>,
|
||||||
|
child_extra_storage: HashMap<Vec<u8>, HashMap<Vec<u8>, Vec<u8>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
GenesisConfig {
|
GenesisConfig {
|
||||||
changes_trie_config: match support_changes_trie {
|
changes_trie_config: match support_changes_trie {
|
||||||
@@ -51,6 +53,7 @@ impl GenesisConfig {
|
|||||||
balances: endowed_accounts.into_iter().map(|a| (a, balance)).collect(),
|
balances: endowed_accounts.into_iter().map(|a| (a, balance)).collect(),
|
||||||
heap_pages_override,
|
heap_pages_override,
|
||||||
extra_storage,
|
extra_storage,
|
||||||
|
child_extra_storage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +78,9 @@ impl GenesisConfig {
|
|||||||
}
|
}
|
||||||
map.insert(twox_128(&b"sys:auth"[..])[..].to_vec(), self.authorities.encode());
|
map.insert(twox_128(&b"sys:auth"[..])[..].to_vec(), self.authorities.encode());
|
||||||
// Finally, add the extra storage entries.
|
// Finally, add the extra storage entries.
|
||||||
for (key, value) in self.extra_storage.iter().cloned() {
|
map.extend(self.extra_storage.clone().into_iter());
|
||||||
map.insert(key, value);
|
|
||||||
}
|
(map, self.child_extra_storage.clone())
|
||||||
(map, Default::default())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user