mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 08:51:09 +00:00
Child trie storage proof (#2433)
* proof on child trie * higher level api for child storage proof * boilerplate for proof from light fetch * actually check proof on light fetch * Do not break former encoding * tabify * tabify2 * Add child trie root tx to full_storage_root transaction. * Shorten long lines. * Temp rename for audit * Make full_storage a trait method * Name back and replace some code with full_storage where it looks fine. * fix indentations, remove unused import * flush child root to top when calculated * impl +1
This commit is contained in:
@@ -324,29 +324,26 @@ where Block: BlockT<Hash=H256>,
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_storage(&mut self, mut top: StorageOverlay, children: ChildrenStorageOverlay) -> Result<H256, client::error::Error> {
|
||||
fn reset_storage(&mut self, top: StorageOverlay, children: ChildrenStorageOverlay) -> Result<H256, client::error::Error> {
|
||||
|
||||
if top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) {
|
||||
return Err(client::error::Error::GenesisInvalid.into());
|
||||
}
|
||||
|
||||
let mut transaction: PrefixedMemoryDB<Blake2Hasher> = Default::default();
|
||||
|
||||
for (child_key, child_map) in children {
|
||||
for child_key in children.keys() {
|
||||
if !well_known_keys::is_child_storage_key(&child_key) {
|
||||
return Err(client::error::Error::GenesisInvalid.into());
|
||||
}
|
||||
|
||||
let (root, is_default, update) = self.old_state.child_storage_root(&child_key, child_map.into_iter().map(|(k, v)| (k, Some(v))));
|
||||
transaction.consolidate(update);
|
||||
|
||||
if !is_default {
|
||||
top.insert(child_key, root);
|
||||
}
|
||||
}
|
||||
|
||||
let (root, update) = self.old_state.storage_root(top.into_iter().map(|(k, v)| (k, Some(v))));
|
||||
transaction.consolidate(update);
|
||||
let child_delta = children.into_iter()
|
||||
.map(|(storage_key, child_overlay)|
|
||||
(storage_key, child_overlay.into_iter().map(|(k, v)| (k, Some(v)))));
|
||||
|
||||
let (root, transaction) = self.old_state.full_storage_root(
|
||||
top.into_iter().map(|(k, v)| (k, Some(v))),
|
||||
child_delta
|
||||
);
|
||||
|
||||
self.db_updates = transaction;
|
||||
Ok(root)
|
||||
|
||||
@@ -52,7 +52,7 @@ use primitives::storage::well_known_keys;
|
||||
use parity_codec::{Encode, Decode};
|
||||
use state_machine::{
|
||||
DBValue, Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId,
|
||||
ExecutionStrategy, ExecutionManager, prove_read,
|
||||
ExecutionStrategy, ExecutionManager, prove_read, prove_child_read,
|
||||
ChangesTrieRootsStorage, ChangesTrieStorage,
|
||||
key_changes, key_changes_proof, OverlayedChanges, NeverOffchainExt,
|
||||
};
|
||||
@@ -374,6 +374,20 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
|
||||
.map_err(Into::into))
|
||||
}
|
||||
|
||||
/// Reads child storage value at a given block + storage_key + key, returning
|
||||
/// read proof.
|
||||
pub fn read_child_proof(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
storage_key: &[u8],
|
||||
key: &[u8]
|
||||
) -> error::Result<Vec<Vec<u8>>> {
|
||||
self.state_at(id)
|
||||
.and_then(|state| prove_child_read(state, storage_key, key)
|
||||
.map(|(_, proof)| proof)
|
||||
.map_err(Into::into))
|
||||
}
|
||||
|
||||
/// Execute a call to a contract on top of state in a block of given hash
|
||||
/// AND returning execution proof.
|
||||
///
|
||||
|
||||
@@ -24,7 +24,7 @@ use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero,
|
||||
NumberFor, As, Digest, DigestItem};
|
||||
use runtime_primitives::{Justification, StorageOverlay, ChildrenStorageOverlay};
|
||||
use state_machine::backend::{Backend as StateBackend, InMemory, Consolidate};
|
||||
use state_machine::backend::{Backend as StateBackend, InMemory};
|
||||
use state_machine::{self, InMemoryChangesTrieStorage, ChangesTrieAnchorBlockId};
|
||||
use hash_db::Hasher;
|
||||
use trie::MemoryDB;
|
||||
@@ -482,22 +482,17 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_storage(&mut self, mut top: StorageOverlay, children: ChildrenStorageOverlay) -> error::Result<H::Out> {
|
||||
fn reset_storage(&mut self, top: StorageOverlay, children: ChildrenStorageOverlay) -> error::Result<H::Out> {
|
||||
check_genesis_storage(&top, &children)?;
|
||||
|
||||
let mut transaction: Vec<(Option<Vec<u8>>, Vec<u8>, Option<Vec<u8>>)> = Default::default();
|
||||
let child_delta = children.into_iter()
|
||||
.map(|(storage_key, child_overlay)|
|
||||
(storage_key, child_overlay.into_iter().map(|(k, v)| (k, Some(v)))));
|
||||
|
||||
for (child_key, child_map) in children {
|
||||
let (root, is_default, update) = self.old_state.child_storage_root(&child_key, child_map.into_iter().map(|(k, v)| (k, Some(v))));
|
||||
transaction.consolidate(update);
|
||||
|
||||
if !is_default {
|
||||
top.insert(child_key, root);
|
||||
}
|
||||
}
|
||||
|
||||
let (root, update) = self.old_state.storage_root(top.into_iter().map(|(k, v)| (k, Some(v))));
|
||||
transaction.consolidate(update);
|
||||
let (root, transaction) = self.old_state.full_storage_root(
|
||||
top.into_iter().map(|(k, v)| (k, Some(v))),
|
||||
child_delta
|
||||
);
|
||||
|
||||
self.new_state = Some(InMemory::from(transaction));
|
||||
Ok(root)
|
||||
|
||||
@@ -25,7 +25,8 @@ use hash_db::{HashDB, Hasher};
|
||||
use primitives::{ChangesTrieConfiguration, convert_hash};
|
||||
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use state_machine::{CodeExecutor, ChangesTrieRootsStorage, ChangesTrieAnchorBlockId,
|
||||
TrieBackend, read_proof_check, key_changes_proof_check, create_proof_check_backend_storage};
|
||||
TrieBackend, read_proof_check, key_changes_proof_check,
|
||||
create_proof_check_backend_storage, read_child_proof_check};
|
||||
|
||||
use crate::cht;
|
||||
use crate::error::{Error as ClientError, Result as ClientResult};
|
||||
@@ -71,6 +72,21 @@ pub struct RemoteReadRequest<Header: HeaderT> {
|
||||
pub retry_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// Remote storage read child request.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RemoteReadChildRequest<Header: HeaderT> {
|
||||
/// Read at state of given block.
|
||||
pub block: Header::Hash,
|
||||
/// Header of block at which read is performed.
|
||||
pub header: Header,
|
||||
/// Storage key for child.
|
||||
pub storage_key: Vec<u8>,
|
||||
/// Child storage key to read.
|
||||
pub key: Vec<u8>,
|
||||
/// Number of times to retry request. None means that default RETRY_COUNT is used.
|
||||
pub retry_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// Remote key changes read request.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RemoteChangesRequest<Header: HeaderT> {
|
||||
@@ -123,7 +139,15 @@ pub trait Fetcher<Block: BlockT>: Send + Sync {
|
||||
/// Fetch remote header.
|
||||
fn remote_header(&self, request: RemoteHeaderRequest<Block::Header>) -> Self::RemoteHeaderResult;
|
||||
/// Fetch remote storage value.
|
||||
fn remote_read(&self, request: RemoteReadRequest<Block::Header>) -> Self::RemoteReadResult;
|
||||
fn remote_read(
|
||||
&self,
|
||||
request: RemoteReadRequest<Block::Header>
|
||||
) -> Self::RemoteReadResult;
|
||||
/// Fetch remote storage child value.
|
||||
fn remote_read_child(
|
||||
&self,
|
||||
request: RemoteReadChildRequest<Block::Header>
|
||||
) -> Self::RemoteReadResult;
|
||||
/// Fetch remote call result.
|
||||
fn remote_call(&self, request: RemoteCallRequest<Block::Header>) -> Self::RemoteCallResult;
|
||||
/// Fetch remote changes ((block number, extrinsic index)) where given key has been changed
|
||||
@@ -149,6 +173,12 @@ pub trait FetchChecker<Block: BlockT>: Send + Sync {
|
||||
request: &RemoteReadRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Option<Vec<u8>>>;
|
||||
/// Check remote storage read proof.
|
||||
fn check_read_child_proof(
|
||||
&self,
|
||||
request: &RemoteReadChildRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Option<Vec<u8>>>;
|
||||
/// Check remote method execution proof.
|
||||
fn check_execution_proof(
|
||||
&self,
|
||||
@@ -338,6 +368,19 @@ impl<E, Block, H, S, F> FetchChecker<Block> for LightDataChecker<E, H, Block, S,
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn check_read_child_proof(
|
||||
&self,
|
||||
request: &RemoteReadChildRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Option<Vec<u8>>> {
|
||||
read_child_proof_check::<H>(
|
||||
convert_hash(request.header.state_root()),
|
||||
remote_proof,
|
||||
&request.storage_key,
|
||||
&request.key)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn check_execution_proof(
|
||||
&self,
|
||||
request: &RemoteCallRequest<Block::Header>,
|
||||
@@ -425,6 +468,10 @@ pub mod tests {
|
||||
err("Not implemented on test node".into())
|
||||
}
|
||||
|
||||
fn remote_read_child(&self, _request: RemoteReadChildRequest<Header>) -> Self::RemoteReadResult {
|
||||
err("Not implemented on test node".into())
|
||||
}
|
||||
|
||||
fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
|
||||
ok((*self.lock()).clone())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub use self::generic::{
|
||||
BlockAnnounce, RemoteCallRequest, RemoteReadRequest,
|
||||
RemoteHeaderRequest, RemoteHeaderResponse,
|
||||
RemoteChangesRequest, RemoteChangesResponse,
|
||||
FromBlock
|
||||
FromBlock, RemoteReadChildRequest,
|
||||
};
|
||||
|
||||
/// A unique ID of a request.
|
||||
@@ -129,8 +129,8 @@ pub mod generic {
|
||||
use runtime_primitives::Justification;
|
||||
use crate::config::Roles;
|
||||
use super::{
|
||||
BlockAttributes, RemoteCallResponse, RemoteReadResponse,
|
||||
RequestId, Transactions, Direction, ConsensusEngineId,
|
||||
RemoteReadResponse, Transactions, Direction,
|
||||
RequestId, BlockAttributes, RemoteCallResponse, ConsensusEngineId,
|
||||
};
|
||||
/// Consensus is mostly opaque to us
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
@@ -198,6 +198,8 @@ pub mod generic {
|
||||
RemoteChangesRequest(RemoteChangesRequest<Hash>),
|
||||
/// Remote changes reponse.
|
||||
RemoteChangesResponse(RemoteChangesResponse<Number, Hash>),
|
||||
/// Remote child storage read request.
|
||||
RemoteReadChildRequest(RemoteReadChildRequest<Hash>),
|
||||
/// Chain-specific message
|
||||
#[codec(index = "255")]
|
||||
ChainSpecific(Vec<u8>),
|
||||
@@ -291,6 +293,19 @@ pub mod generic {
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote storage read child request.
|
||||
pub struct RemoteReadChildRequest<H> {
|
||||
/// Unique request id.
|
||||
pub id: RequestId,
|
||||
/// Block at which to perform call.
|
||||
pub block: H,
|
||||
/// Child Storage key.
|
||||
pub storage_key: Vec<u8>,
|
||||
/// Storage key.
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote header request.
|
||||
pub struct RemoteHeaderRequest<N> {
|
||||
|
||||
@@ -27,7 +27,8 @@ use linked_hash_map::Entry;
|
||||
use parking_lot::Mutex;
|
||||
use client::error::Error as ClientError;
|
||||
use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest,
|
||||
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof};
|
||||
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof,
|
||||
RemoteReadChildRequest};
|
||||
use crate::message;
|
||||
use network_libp2p::PeerId;
|
||||
use crate::config::Roles;
|
||||
@@ -107,6 +108,10 @@ struct Request<Block: BlockT> {
|
||||
enum RequestData<Block: BlockT> {
|
||||
RemoteHeader(RemoteHeaderRequest<Block::Header>, OneShotSender<Result<Block::Header, ClientError>>),
|
||||
RemoteRead(RemoteReadRequest<Block::Header>, OneShotSender<Result<Option<Vec<u8>>, ClientError>>),
|
||||
RemoteReadChild(
|
||||
RemoteReadChildRequest<Block::Header>,
|
||||
OneShotSender<Result<Option<Vec<u8>>, ClientError>>
|
||||
),
|
||||
RemoteCall(RemoteCallRequest<Block::Header>, OneShotSender<Result<Vec<u8>, ClientError>>),
|
||||
RemoteChanges(RemoteChangesRequest<Block::Header>, OneShotSender<Result<Vec<(NumberFor<Block>, u32)>, ClientError>>),
|
||||
}
|
||||
@@ -271,14 +276,30 @@ impl<B> OnDemandService<B> for OnDemand<B> where
|
||||
|
||||
fn on_remote_read_response(&self, peer: PeerId, response: message::RemoteReadResponse) {
|
||||
self.accept_response("read", peer, response.id, |request| match request.data {
|
||||
RequestData::RemoteRead(request, sender) => match self.checker.check_read_proof(&request, response.proof) {
|
||||
Ok(response) => {
|
||||
// we do not bother if receiver has been dropped already
|
||||
let _ = sender.send(Ok(response));
|
||||
Accept::Ok
|
||||
},
|
||||
Err(error) => Accept::CheckFailed(error, RequestData::RemoteRead(request, sender)),
|
||||
},
|
||||
RequestData::RemoteRead(request, sender) => {
|
||||
match self.checker.check_read_proof(&request, response.proof) {
|
||||
Ok(response) => {
|
||||
// we do not bother if receiver has been dropped already
|
||||
let _ = sender.send(Ok(response));
|
||||
Accept::Ok
|
||||
},
|
||||
Err(error) => Accept::CheckFailed(
|
||||
error,
|
||||
RequestData::RemoteRead(request, sender)
|
||||
),
|
||||
}},
|
||||
RequestData::RemoteReadChild(request, sender) => {
|
||||
match self.checker.check_read_child_proof(&request, response.proof) {
|
||||
Ok(response) => {
|
||||
// we do not bother if receiver has been dropped already
|
||||
let _ = sender.send(Ok(response));
|
||||
Accept::Ok
|
||||
},
|
||||
Err(error) => Accept::CheckFailed(
|
||||
error,
|
||||
RequestData::RemoteReadChild(request, sender)
|
||||
),
|
||||
}},
|
||||
data => Accept::Unexpected(data),
|
||||
})
|
||||
}
|
||||
@@ -335,8 +356,23 @@ impl<B> Fetcher<B> for OnDemand<B> where
|
||||
|
||||
fn remote_read(&self, request: RemoteReadRequest<B::Header>) -> Self::RemoteReadResult {
|
||||
let (sender, receiver) = channel();
|
||||
self.schedule_request(request.retry_count.clone(), RequestData::RemoteRead(request, sender),
|
||||
RemoteResponse { receiver })
|
||||
self.schedule_request(
|
||||
request.retry_count.clone(),
|
||||
RequestData::RemoteRead(request, sender),
|
||||
RemoteResponse { receiver }
|
||||
)
|
||||
}
|
||||
|
||||
fn remote_read_child(
|
||||
&self,
|
||||
request: RemoteReadChildRequest<B::Header>
|
||||
) -> Self::RemoteReadResult {
|
||||
let (sender, receiver) = channel();
|
||||
self.schedule_request(
|
||||
request.retry_count.clone(),
|
||||
RequestData::RemoteReadChild(request, sender),
|
||||
RemoteResponse { receiver }
|
||||
)
|
||||
}
|
||||
|
||||
fn remote_call(&self, request: RemoteCallRequest<B::Header>) -> Self::RemoteCallResult {
|
||||
@@ -477,6 +513,7 @@ impl<Block: BlockT> Request<Block> {
|
||||
match self.data {
|
||||
RequestData::RemoteHeader(ref data, _) => data.block,
|
||||
RequestData::RemoteRead(ref data, _) => *data.header.number(),
|
||||
RequestData::RemoteReadChild(ref data, _) => *data.header.number(),
|
||||
RequestData::RemoteCall(ref data, _) => *data.header.number(),
|
||||
RequestData::RemoteChanges(ref data, _) => data.max_block.0,
|
||||
}
|
||||
@@ -495,6 +532,14 @@ impl<Block: BlockT> Request<Block> {
|
||||
block: data.block,
|
||||
key: data.key.clone(),
|
||||
}),
|
||||
RequestData::RemoteReadChild(ref data, _) =>
|
||||
message::generic::Message::RemoteReadChildRequest(
|
||||
message::RemoteReadChildRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
storage_key: data.storage_key.clone(),
|
||||
key: data.key.clone(),
|
||||
}),
|
||||
RequestData::RemoteCall(ref data, _) =>
|
||||
message::generic::Message::RemoteCallRequest(message::RemoteCallRequest {
|
||||
id: self.id,
|
||||
@@ -522,6 +567,7 @@ impl<Block: BlockT> RequestData<Block> {
|
||||
RequestData::RemoteHeader(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
RequestData::RemoteCall(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
RequestData::RemoteRead(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
RequestData::RemoteReadChild(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
RequestData::RemoteChanges(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
}
|
||||
}
|
||||
@@ -535,7 +581,8 @@ pub mod tests {
|
||||
use runtime_primitives::traits::NumberFor;
|
||||
use client::{error::{Error as ClientError, Result as ClientResult}};
|
||||
use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest,
|
||||
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof};
|
||||
ChangesProof, RemoteCallRequest, RemoteReadRequest,
|
||||
RemoteReadChildRequest, RemoteChangesRequest};
|
||||
use crate::config::Roles;
|
||||
use crate::message;
|
||||
use network_libp2p::PeerId;
|
||||
@@ -566,6 +613,17 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_read_child_proof(
|
||||
&self,
|
||||
_: &RemoteReadChildRequest<Header>,
|
||||
_: Vec<Vec<u8>>
|
||||
) -> ClientResult<Option<Vec<u8>>> {
|
||||
match self.ok {
|
||||
true => Ok(Some(vec![42])),
|
||||
false => Err(ClientError::Backend("Test error".into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_execution_proof(&self, _: &RemoteCallRequest<Header>, _: Vec<Vec<u8>>) -> ClientResult<Vec<u8>> {
|
||||
match self.ok {
|
||||
true => Ok(vec![42]),
|
||||
@@ -847,6 +905,34 @@ pub mod tests {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_read_child_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let (network_sender, _network_port) = network_channel();
|
||||
let peer0 = PeerId::random();
|
||||
on_demand.set_network_sender(network_sender.clone());
|
||||
on_demand.on_connect(peer0.clone(), Roles::FULL, 1000);
|
||||
|
||||
let response = on_demand.remote_read_child(RemoteReadChildRequest {
|
||||
header: dummy_header(),
|
||||
block: Default::default(),
|
||||
storage_key: b":child_storage:sub".to_vec(),
|
||||
key: b":key".to_vec(),
|
||||
retry_count: None,
|
||||
});
|
||||
let thread = ::std::thread::spawn(move || {
|
||||
let result = response.wait().unwrap();
|
||||
assert_eq!(result, Some(vec![42]));
|
||||
});
|
||||
|
||||
on_demand.on_remote_read_response(
|
||||
peer0.clone(), message::RemoteReadResponse {
|
||||
id: 0,
|
||||
proof: vec![vec![2]],
|
||||
});
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_header_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
|
||||
@@ -70,6 +70,7 @@ pub trait Backend<H: Hasher> {
|
||||
|
||||
/// Calculate the storage root, with given delta over what is already stored in
|
||||
/// the backend, and produce a "transaction" that can be used to commit.
|
||||
/// Does not include child storage updates.
|
||||
fn storage_root<I>(&self, delta: I) -> (H::Out, Self::Transaction)
|
||||
where
|
||||
I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>,
|
||||
@@ -91,6 +92,41 @@ pub trait Backend<H: Hasher> {
|
||||
|
||||
/// Try convert into trie backend.
|
||||
fn try_into_trie_backend(self) -> Option<TrieBackend<Self::TrieBackendStorage, H>>;
|
||||
|
||||
/// Calculate the storage root, with given delta over what is already stored
|
||||
/// in the backend, and produce a "transaction" that can be used to commit.
|
||||
/// Does include child storage updates.
|
||||
fn full_storage_root<I1, I2i, I2>(
|
||||
&self,
|
||||
delta: I1,
|
||||
child_deltas: I2)
|
||||
-> (H::Out, Self::Transaction)
|
||||
where
|
||||
I1: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>,
|
||||
I2i: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>,
|
||||
I2: IntoIterator<Item=(Vec<u8>, I2i)>,
|
||||
<H as Hasher>::Out: Ord,
|
||||
{
|
||||
let mut txs: Self::Transaction = Default::default();
|
||||
let mut child_roots: Vec<_> = Default::default();
|
||||
// child first
|
||||
for (storage_key, child_delta) in child_deltas {
|
||||
let (child_root, empty, child_txs) =
|
||||
self.child_storage_root(&storage_key[..], child_delta);
|
||||
txs.consolidate(child_txs);
|
||||
if empty {
|
||||
child_roots.push((storage_key, None));
|
||||
} else {
|
||||
child_roots.push((storage_key, Some(child_root)));
|
||||
}
|
||||
}
|
||||
let (root, parent_txs) = self.storage_root(
|
||||
delta.into_iter().chain(child_roots.into_iter())
|
||||
);
|
||||
txs.consolidate(parent_txs);
|
||||
(root, txs)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Trait that allows consolidate two transactions together.
|
||||
@@ -213,6 +249,13 @@ impl<H> From<Vec<(Option<Vec<u8>>, Vec<u8>, Option<Vec<u8>>)>> for InMemory<H> {
|
||||
|
||||
impl super::Error for Void {}
|
||||
|
||||
impl<H: Hasher> InMemory<H> {
|
||||
/// child storage key iterator
|
||||
pub fn child_storage_keys(&self) -> impl Iterator<Item=&[u8]> {
|
||||
self.inner.iter().filter_map(|item| item.0.as_ref().map(|v|&v[..]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> Backend<H> for InMemory<H> {
|
||||
type Error = Void;
|
||||
type Transaction = Vec<(Option<Vec<u8>>, Vec<u8>, Option<Vec<u8>>)>;
|
||||
@@ -290,16 +333,28 @@ impl<H: Hasher> Backend<H> for InMemory<H> {
|
||||
self.inner.get(&None).into_iter().flat_map(|map| map.keys().filter(|k| k.starts_with(prefix)).cloned()).collect()
|
||||
}
|
||||
|
||||
fn try_into_trie_backend(self) -> Option<TrieBackend<Self::TrieBackendStorage, H>> {
|
||||
fn try_into_trie_backend(
|
||||
self
|
||||
)-> Option<TrieBackend<Self::TrieBackendStorage, H>> {
|
||||
let mut mdb = MemoryDB::default();
|
||||
let mut root = None;
|
||||
let mut new_child_roots = Vec::new();
|
||||
let mut root_map = None;
|
||||
for (storage_key, map) in self.inner {
|
||||
if storage_key != None {
|
||||
let _ = insert_into_memory_db::<H, _>(&mut mdb, map.into_iter())?;
|
||||
if let Some(storage_key) = storage_key.as_ref() {
|
||||
let ch = insert_into_memory_db::<H, _>(&mut mdb, map.into_iter())?;
|
||||
new_child_roots.push((storage_key.clone(), ch.as_ref().into()));
|
||||
} else {
|
||||
root = Some(insert_into_memory_db::<H, _>(&mut mdb, map.into_iter())?);
|
||||
root_map = Some(map);
|
||||
}
|
||||
}
|
||||
// root handling
|
||||
if let Some(map) = root_map.take() {
|
||||
root = Some(insert_into_memory_db::<H, _>(
|
||||
&mut mdb,
|
||||
map.into_iter().chain(new_child_roots.into_iter())
|
||||
)?);
|
||||
}
|
||||
let root = match root {
|
||||
Some(root) => root,
|
||||
None => insert_into_memory_db::<H, _>(&mut mdb, ::std::iter::empty())?,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
use std::{error, fmt, cmp::Ord};
|
||||
use log::warn;
|
||||
use crate::backend::{Backend, Consolidate};
|
||||
use crate::backend::Backend;
|
||||
use crate::changes_trie::{AnchorBlockId, Storage as ChangesTrieStorage, compute_changes_trie_root};
|
||||
use crate::{Externalities, OverlayedChanges, OffchainExt, ChildStorageKey};
|
||||
use hash_db::Hasher;
|
||||
@@ -134,30 +134,6 @@ where
|
||||
self.storage_transaction = None;
|
||||
}
|
||||
|
||||
/// Fetch child storage root together with its transaction.
|
||||
fn child_storage_root_transaction(&mut self, storage_key: &[u8]) -> (Vec<u8>, B::Transaction) {
|
||||
self.mark_dirty();
|
||||
|
||||
let (root, is_default, transaction) = {
|
||||
let delta = self.overlay.committed.children.get(storage_key)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone())))
|
||||
.chain(self.overlay.prospective.children.get(storage_key)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone()))));
|
||||
|
||||
self.backend.child_storage_root(storage_key, delta)
|
||||
};
|
||||
|
||||
let root_val = if is_default {
|
||||
None
|
||||
} else {
|
||||
Some(root.clone())
|
||||
};
|
||||
self.overlay.sync_child_storage_root(storage_key, root_val);
|
||||
|
||||
(root, transaction)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -288,21 +264,24 @@ where
|
||||
return root.clone();
|
||||
}
|
||||
|
||||
let mut transaction = B::Transaction::default();
|
||||
let child_storage_keys: std::collections::BTreeSet<_> = self.overlay.prospective.children.keys().cloned()
|
||||
.chain(self.overlay.committed.children.keys().cloned()).collect();
|
||||
let child_storage_keys =
|
||||
self.overlay.prospective.children.keys()
|
||||
.chain(self.overlay.committed.children.keys());
|
||||
|
||||
let child_delta_iter = child_storage_keys.map(|storage_key|
|
||||
(storage_key.clone(), self.overlay.committed.children.get(storage_key)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone())))
|
||||
.chain(self.overlay.prospective.children.get(storage_key)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone()))))));
|
||||
|
||||
for key in child_storage_keys {
|
||||
let (_, t) = self.child_storage_root_transaction(&key);
|
||||
transaction.consolidate(t);
|
||||
}
|
||||
|
||||
// compute and memoize
|
||||
let delta = self.overlay.committed.top.iter().map(|(k, v)| (k.clone(), v.value.clone()))
|
||||
.chain(self.overlay.prospective.top.iter().map(|(k, v)| (k.clone(), v.value.clone())));
|
||||
|
||||
let (root, t) = self.backend.storage_root(delta);
|
||||
transaction.consolidate(t);
|
||||
let (root, transaction) = self.backend.full_storage_root(delta, child_delta_iter);
|
||||
self.storage_transaction = Some((transaction, root));
|
||||
root
|
||||
}
|
||||
@@ -316,7 +295,21 @@ where
|
||||
default_child_trie_root::<H>(storage_key.as_ref())
|
||||
)
|
||||
} else {
|
||||
self.child_storage_root_transaction(storage_key.as_ref()).0
|
||||
let storage_key = storage_key.as_ref();
|
||||
|
||||
let delta = self.overlay.committed.children.get(storage_key)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone())))
|
||||
.chain(self.overlay.prospective.children.get(storage_key)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.1.iter().map(|(k, v)| (k.clone(), v.clone()))));
|
||||
|
||||
let root = self.backend.child_storage_root(storage_key, delta).0;
|
||||
|
||||
self.overlay.set_storage(storage_key.to_vec(), Some(root.to_vec()));
|
||||
|
||||
root
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -631,7 +631,7 @@ where
|
||||
Exec: CodeExecutor<H>,
|
||||
H::Out: Ord,
|
||||
{
|
||||
let trie_backend = proving_backend::create_proof_check_backend::<H>(root.into(), proof)?;
|
||||
let trie_backend = create_proof_check_backend::<H>(root.into(), proof)?;
|
||||
execution_proof_check_on_trie_backend(&trie_backend, overlay, exec, method, call_data)
|
||||
}
|
||||
|
||||
@@ -676,10 +676,29 @@ where
|
||||
H::Out: Ord
|
||||
{
|
||||
let trie_backend = backend.try_into_trie_backend()
|
||||
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?;
|
||||
.ok_or_else(
|
||||
||Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>
|
||||
)?;
|
||||
prove_read_on_trie_backend(&trie_backend, key)
|
||||
}
|
||||
|
||||
/// Generate child storage read proof.
|
||||
pub fn prove_child_read<B, H>(
|
||||
backend: B,
|
||||
storage_key: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<(Option<Vec<u8>>, Vec<Vec<u8>>), Box<Error>>
|
||||
where
|
||||
B: Backend<H>,
|
||||
H: Hasher,
|
||||
H::Out: Ord
|
||||
{
|
||||
let trie_backend = backend.try_into_trie_backend()
|
||||
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?;
|
||||
prove_child_read_on_trie_backend(&trie_backend, storage_key, key)
|
||||
}
|
||||
|
||||
|
||||
/// Generate storage read proof on pre-created trie backend.
|
||||
pub fn prove_read_on_trie_backend<S, H>(
|
||||
trie_backend: &TrieBackend<S, H>,
|
||||
@@ -695,6 +714,23 @@ where
|
||||
Ok((result, proving_backend.extract_proof()))
|
||||
}
|
||||
|
||||
/// Generate storage read proof on pre-created trie backend.
|
||||
pub fn prove_child_read_on_trie_backend<S, H>(
|
||||
trie_backend: &TrieBackend<S, H>,
|
||||
storage_key: &[u8],
|
||||
key: &[u8]
|
||||
) -> Result<(Option<Vec<u8>>, Vec<Vec<u8>>), Box<Error>>
|
||||
where
|
||||
S: trie_backend_essence::TrieBackendStorage<H>,
|
||||
H: Hasher,
|
||||
H::Out: Ord
|
||||
{
|
||||
let proving_backend = proving_backend::ProvingBackend::<_, H>::new(trie_backend);
|
||||
let result = proving_backend.child_storage(storage_key, key)
|
||||
.map_err(|e| Box::new(e) as Box<Error>)?;
|
||||
Ok((result, proving_backend.extract_proof()))
|
||||
}
|
||||
|
||||
/// Check storage read proof, generated by `prove_read` call.
|
||||
pub fn read_proof_check<H>(
|
||||
root: H::Out,
|
||||
@@ -705,10 +741,26 @@ where
|
||||
H: Hasher,
|
||||
H::Out: Ord
|
||||
{
|
||||
let proving_backend = proving_backend::create_proof_check_backend::<H>(root, proof)?;
|
||||
let proving_backend = create_proof_check_backend::<H>(root, proof)?;
|
||||
read_proof_check_on_proving_backend(&proving_backend, key)
|
||||
}
|
||||
|
||||
/// Check child storage read proof, generated by `prove_child_read` call.
|
||||
pub fn read_child_proof_check<H>(
|
||||
root: H::Out,
|
||||
proof: Vec<Vec<u8>>,
|
||||
storage_key: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<Option<Vec<u8>>, Box<Error>>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord
|
||||
{
|
||||
let proving_backend = create_proof_check_backend::<H>(root, proof)?;
|
||||
read_child_proof_check_on_proving_backend(&proving_backend, storage_key, key)
|
||||
}
|
||||
|
||||
|
||||
/// Check storage read proof on pre-created proving backend.
|
||||
pub fn read_proof_check_on_proving_backend<H>(
|
||||
proving_backend: &TrieBackend<MemoryDB<H>, H>,
|
||||
@@ -721,6 +773,19 @@ where
|
||||
proving_backend.storage(key).map_err(|e| Box::new(e) as Box<Error>)
|
||||
}
|
||||
|
||||
/// Check child storage read proof on pre-created proving backend.
|
||||
pub fn read_child_proof_check_on_proving_backend<H>(
|
||||
proving_backend: &TrieBackend<MemoryDB<H>, H>,
|
||||
storage_key: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<Option<Vec<u8>>, Box<Error>>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord
|
||||
{
|
||||
proving_backend.child_storage(storage_key, key).map_err(|e| Box::new(e) as Box<Error>)
|
||||
}
|
||||
|
||||
/// Sets overlayed changes' changes trie configuration. Returns error if configuration
|
||||
/// differs from previous OR config decode has failed.
|
||||
pub(crate) fn set_changes_trie_config(overlay: &mut OverlayedChanges, config: Option<Vec<u8>>, final_check: bool) -> Result<(), Box<Error>> {
|
||||
@@ -1001,11 +1066,40 @@ mod tests {
|
||||
let remote_root = remote_backend.storage_root(::std::iter::empty()).0;
|
||||
let remote_proof = prove_read(remote_backend, b"value2").unwrap().1;
|
||||
// check proof locally
|
||||
let local_result1 = read_proof_check::<Blake2Hasher>(remote_root, remote_proof.clone(), b"value2").unwrap();
|
||||
let local_result2 = read_proof_check::<Blake2Hasher>(remote_root, remote_proof.clone(), &[0xff]).is_ok();
|
||||
let local_result1 = read_proof_check::<Blake2Hasher>(
|
||||
remote_root,
|
||||
remote_proof.clone(),
|
||||
b"value2"
|
||||
).unwrap();
|
||||
let local_result2 = read_proof_check::<Blake2Hasher>(
|
||||
remote_root,
|
||||
remote_proof.clone(),
|
||||
&[0xff]
|
||||
).is_ok();
|
||||
// check that results are correct
|
||||
assert_eq!(local_result1, Some(vec![24]));
|
||||
assert_eq!(local_result2, false);
|
||||
// on child trie
|
||||
let remote_backend = trie_backend::tests::test_trie();
|
||||
let remote_root = remote_backend.storage_root(::std::iter::empty()).0;
|
||||
let remote_proof = prove_child_read(
|
||||
remote_backend,
|
||||
b":child_storage:default:sub1",
|
||||
b"value3"
|
||||
).unwrap().1;
|
||||
let local_result1 = read_child_proof_check::<Blake2Hasher>(
|
||||
remote_root,
|
||||
remote_proof.clone(),
|
||||
b":child_storage:default:sub1",b"value3"
|
||||
).unwrap();
|
||||
let local_result2 = read_child_proof_check::<Blake2Hasher>(
|
||||
remote_root,
|
||||
remote_proof.clone(),
|
||||
b":child_storage:default:sub1",
|
||||
b"value2"
|
||||
).unwrap();
|
||||
assert_eq!(local_result1, Some(vec![142]));
|
||||
assert_eq!(local_result2, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -159,19 +159,6 @@ impl OverlayedChanges {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync the child storage root.
|
||||
pub(crate) fn sync_child_storage_root(&mut self, storage_key: &[u8], root: Option<Vec<u8>>) {
|
||||
let entry = self.prospective.top.entry(storage_key.to_vec()).or_default();
|
||||
entry.value = root;
|
||||
|
||||
if let Some((Some(extrinsics), _)) = self.prospective.children.get(storage_key) {
|
||||
for extrinsic in extrinsics {
|
||||
entry.extrinsics.get_or_insert_with(Default::default)
|
||||
.insert(*extrinsic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear child storage of given storage key.
|
||||
///
|
||||
/// NOTE that this doesn't take place immediately but written into the prospective
|
||||
|
||||
@@ -222,6 +222,7 @@ mod tests {
|
||||
use crate::trie_backend::tests::test_trie;
|
||||
use super::*;
|
||||
use primitives::{Blake2Hasher};
|
||||
use crate::ChildStorageKey;
|
||||
|
||||
fn test_proving<'a>(trie_backend: &'a TrieBackend<PrefixedMemoryDB<Blake2Hasher>, Blake2Hasher>) -> ProvingBackend<'a, PrefixedMemoryDB<Blake2Hasher>, Blake2Hasher> {
|
||||
ProvingBackend::new(trie_backend)
|
||||
@@ -281,4 +282,75 @@ mod tests {
|
||||
let proof_check = create_proof_check_backend::<Blake2Hasher>(in_memory_root.into(), proof).unwrap();
|
||||
assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_recorded_and_checked_with_child() {
|
||||
let subtrie1 = ChildStorageKey::<Blake2Hasher>::from_slice(
|
||||
b":child_storage:default:sub1"
|
||||
).unwrap();
|
||||
let subtrie2 = ChildStorageKey::<Blake2Hasher>::from_slice(
|
||||
b":child_storage:default:sub2"
|
||||
).unwrap();
|
||||
let own1 = subtrie1.into_owned();
|
||||
let own2 = subtrie2.into_owned();
|
||||
let contents = (0..64).map(|i| (None, vec![i], Some(vec![i])))
|
||||
.chain((28..65).map(|i| (Some(own1.clone()), vec![i], Some(vec![i]))))
|
||||
.chain((10..15).map(|i| (Some(own2.clone()), vec![i], Some(vec![i]))))
|
||||
.collect::<Vec<_>>();
|
||||
let in_memory = InMemory::<Blake2Hasher>::default();
|
||||
let in_memory = in_memory.update(contents);
|
||||
let in_memory_root = in_memory.full_storage_root::<_, Vec<_>, _>(
|
||||
::std::iter::empty(),
|
||||
in_memory.child_storage_keys().map(|k|(k.to_vec(), Vec::new()))
|
||||
).0;
|
||||
(0..64).for_each(|i| assert_eq!(
|
||||
in_memory.storage(&[i]).unwrap().unwrap(),
|
||||
vec![i]
|
||||
));
|
||||
(28..65).for_each(|i| assert_eq!(
|
||||
in_memory.child_storage(&own1[..], &[i]).unwrap().unwrap(),
|
||||
vec![i]
|
||||
));
|
||||
(10..15).for_each(|i| assert_eq!(
|
||||
in_memory.child_storage(&own2[..], &[i]).unwrap().unwrap(),
|
||||
vec![i]
|
||||
));
|
||||
|
||||
let trie = in_memory.try_into_trie_backend().unwrap();
|
||||
let trie_root = trie.storage_root(::std::iter::empty()).0;
|
||||
assert_eq!(in_memory_root, trie_root);
|
||||
(0..64).for_each(|i| assert_eq!(
|
||||
trie.storage(&[i]).unwrap().unwrap(),
|
||||
vec![i]
|
||||
));
|
||||
|
||||
let proving = ProvingBackend::new(&trie);
|
||||
assert_eq!(proving.storage(&[42]).unwrap().unwrap(), vec![42]);
|
||||
|
||||
let proof = proving.extract_proof();
|
||||
|
||||
let proof_check = create_proof_check_backend::<Blake2Hasher>(
|
||||
in_memory_root.into(),
|
||||
proof
|
||||
).unwrap();
|
||||
assert!(proof_check.storage(&[0]).is_err());
|
||||
assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]);
|
||||
// note that it is include in root because proof close
|
||||
assert_eq!(proof_check.storage(&[41]).unwrap().unwrap(), vec![41]);
|
||||
assert_eq!(proof_check.storage(&[64]).unwrap(), None);
|
||||
|
||||
let proving = ProvingBackend::new(&trie);
|
||||
assert_eq!(proving.child_storage(&own1[..], &[64]), Ok(Some(vec![64])));
|
||||
|
||||
let proof = proving.extract_proof();
|
||||
let proof_check = create_proof_check_backend::<Blake2Hasher>(
|
||||
in_memory_root.into(),
|
||||
proof
|
||||
).unwrap();
|
||||
assert_eq!(
|
||||
proof_check.child_storage(&own1[..], &[64]).unwrap().unwrap(),
|
||||
vec![64]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ impl<S: TrieBackendStorage<H>, H: Hasher> Backend<H> for TrieBackend<S, H> where
|
||||
pub mod tests {
|
||||
use std::collections::HashSet;
|
||||
use primitives::{Blake2Hasher, H256};
|
||||
use parity_codec::Encode;
|
||||
use trie::{TrieMut, TrieDBMut, PrefixedMemoryDB};
|
||||
use super::*;
|
||||
|
||||
@@ -196,6 +197,15 @@ pub mod tests {
|
||||
let mut mdb = PrefixedMemoryDB::<Blake2Hasher>::default();
|
||||
{
|
||||
let mut trie = TrieDBMut::new(&mut mdb, &mut root);
|
||||
trie.insert(b"value3", &[142]).expect("insert failed");
|
||||
trie.insert(b"value4", &[124]).expect("insert failed");
|
||||
};
|
||||
|
||||
{
|
||||
let mut sub_root = Vec::new();
|
||||
root.encode_to(&mut sub_root);
|
||||
let mut trie = TrieDBMut::new(&mut mdb, &mut root);
|
||||
trie.insert(b":child_storage:default:sub1", &sub_root).expect("insert failed");
|
||||
trie.insert(b"key", b"value").expect("insert failed");
|
||||
trie.insert(b"value1", &[42]).expect("insert failed");
|
||||
trie.insert(b"value2", &[24]).expect("insert failed");
|
||||
|
||||
@@ -303,6 +303,13 @@ impl<Block: BlockT> client::light::fetcher::Fetcher<Block> for LightFetcher {
|
||||
unimplemented!("not (yet) used in tests")
|
||||
}
|
||||
|
||||
fn remote_read_child(
|
||||
&self,
|
||||
_request: client::light::fetcher::RemoteReadChildRequest<Block::Header>,
|
||||
) -> Self::RemoteReadResult {
|
||||
unimplemented!("not (yet) used in tests")
|
||||
}
|
||||
|
||||
fn remote_call(
|
||||
&self,
|
||||
_request: client::light::fetcher::RemoteCallRequest<Block::Header>,
|
||||
|
||||
@@ -59,7 +59,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
spec_version: 76,
|
||||
impl_version: 76,
|
||||
impl_version: 77,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user