Make CallApiAt::call_api_at work at light client (#1213)

* light client contextual method call

* fixed off-by-one in test

* newlines + commas
This commit is contained in:
Svyatoslav Nikolsky
2018-12-10 16:02:00 +03:00
committed by GitHub
parent acf1b77bcd
commit 742cb33d90
7 changed files with 341 additions and 132 deletions
+17 -5
View File
@@ -36,11 +36,6 @@ pub trait HeaderBackend<Block: BlockT>: Send + Sync {
/// Get block hash by number. Returns `None` if the header is not in the chain.
fn hash(&self, number: NumberFor<Block>) -> Result<Option<Block::Hash>>;
/// Get block header. Returns `UnknownBlock` error if block is not found.
fn expect_header(&self, id: BlockId<Block>) -> Result<Block::Header> {
self.header(id)?.ok_or_else(|| ErrorKind::UnknownBlock(format!("{}", id)).into())
}
/// Convert an arbitrary block ID into a block hash.
fn block_hash_from_id(&self, id: &BlockId<Block>) -> Result<Option<Block::Hash>> {
match *id {
@@ -56,6 +51,23 @@ pub trait HeaderBackend<Block: BlockT>: Send + Sync {
BlockId::Number(n) => Ok(Some(n)),
}
}
/// Get block header. Returns `UnknownBlock` error if block is not found.
fn expect_header(&self, id: BlockId<Block>) -> Result<Block::Header> {
self.header(id)?.ok_or_else(|| ErrorKind::UnknownBlock(format!("{}", id)).into())
}
/// Convert an arbitrary block ID into a block number. Returns `UnknownBlock` error if block is not found.
fn expect_block_number_from_id(&self, id: &BlockId<Block>) -> Result<NumberFor<Block>> {
self.block_number_from_id(id)
.and_then(|n| n.ok_or_else(|| ErrorKind::UnknownBlock(format!("{}", id)).into()))
}
/// Convert an arbitrary block ID into a block hash. Returns `UnknownBlock` error if block is not found.
fn expect_block_hash_from_id(&self, id: &BlockId<Block>) -> Result<Block::Hash> {
self.block_hash_from_id(id)
.and_then(|n| n.ok_or_else(|| ErrorKind::UnknownBlock(format!("{}", id)).into()))
}
}
/// Blockchain database backend. Does not perform any validation.
+70 -9
View File
@@ -16,6 +16,7 @@
use std::sync::Arc;
use std::cmp::Ord;
use codec::Encode;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::Block as BlockT;
use state_machine::{self, OverlayedChanges, Ext,
@@ -45,7 +46,6 @@ where
B: BlockT,
H: Hasher<Out=B::Hash>,
H::Out: Ord,
{
/// Externalities error type.
type Error: state_machine::Error;
@@ -53,12 +53,32 @@ where
/// Execute a call to a contract on top of state in a block of given hash.
///
/// No changes are made.
fn call(&self,
fn call(
&self,
id: &BlockId<B>,
method: &str,
call_data: &[u8],
) -> Result<CallResult, error::Error>;
/// Execute a contextual call on top of state in a block of a given hash.
///
/// No changes are made.
/// Before executing the method, passed header is installed as the current header
/// of the execution context.
fn contextual_call<
PB: Fn() -> error::Result<B::Header>,
EM: Fn(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
>(
&self,
at: &BlockId<B>,
method: &str,
call_data: &[u8],
changes: &mut OverlayedChanges,
initialised_block: &mut Option<BlockId<B>>,
prepare_environment_block: PB,
manager: ExecutionManager<EM>,
) -> error::Result<Vec<u8>> where ExecutionManager<EM>: Clone;
/// Extract RuntimeVersion of given block
///
/// No changes are made.
@@ -81,11 +101,27 @@ where
/// Execute a call to a contract on top of given state, gathering execution proof.
///
/// No changes are made.
fn prove_at_state<S: state_machine::Backend<H>>(&self,
fn prove_at_state<S: state_machine::Backend<H>>(
&self,
state: S,
overlay: &mut OverlayedChanges,
method: &str,
call_data: &[u8]
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> {
let trie_state = state.try_into_trie_backend()
.ok_or_else(|| Box::new(state_machine::ExecutionError::UnableToGenerateProof) as Box<state_machine::Error>)?;
self.prove_at_trie_state(&trie_state, overlay, method, call_data)
}
/// Execute a call to a contract on top of given trie state, gathering execution proof.
///
/// No changes are made.
fn prove_at_trie_state<S: state_machine::TrieBackendStorage<H>>(
&self,
trie_state: &state_machine::TrieBackend<S, H>,
overlay: &mut OverlayedChanges,
method: &str,
call_data: &[u8]
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error>;
/// Get runtime version if supported.
@@ -139,6 +175,30 @@ where
Ok(CallResult { return_data, changes })
}
fn contextual_call<
PB: Fn() -> error::Result<Block::Header>,
EM: Fn(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
>(
&self,
at: &BlockId<Block>,
method: &str,
call_data: &[u8],
changes: &mut OverlayedChanges,
initialised_block: &mut Option<BlockId<Block>>,
prepare_environment_block: PB,
manager: ExecutionManager<EM>,
) -> Result<Vec<u8>, error::Error> where ExecutionManager<EM>: Clone {
let state = self.backend.state_at(*at)?;
//TODO: Find a better way to prevent double block initialization
if method != "Core_initialise_block" && initialised_block.map(|id| id != *at).unwrap_or(true) {
let header = prepare_environment_block()?;
self.call_at_state(&state, changes, "Core_initialise_block", &header.encode(), manager.clone())?;
*initialised_block = Some(*at);
}
self.call_at_state(&state, changes, method, call_data, manager).map(|cr| cr.0)
}
fn runtime_version(&self, id: &BlockId<Block>) -> error::Result<RuntimeVersion> {
let mut overlay = OverlayedChanges::default();
let state = self.backend.state_at(*id)?;
@@ -178,15 +238,16 @@ where
).map_err(Into::into)
}
fn prove_at_state<S: state_machine::Backend<Blake2Hasher>>(&self,
state: S,
changes: &mut OverlayedChanges,
fn prove_at_trie_state<S: state_machine::TrieBackendStorage<Blake2Hasher>>(
&self,
trie_state: &state_machine::TrieBackend<S, Blake2Hasher>,
overlay: &mut OverlayedChanges,
method: &str,
call_data: &[u8]
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> {
state_machine::prove_execution(
state,
changes,
state_machine::prove_execution_on_trie_backend(
trie_state,
overlay,
&self.executor,
method,
call_data,
+40 -65
View File
@@ -43,14 +43,13 @@ use state_machine::{
ChangesTrieRootsStorage, ChangesTrieStorage,
key_changes, key_changes_proof, OverlayedChanges
};
use codec::Encode;
use backend::{self, BlockImportOperation};
use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend as ChainHeaderBackend};
use call_executor::{CallExecutor, LocalCallExecutor};
use executor::{RuntimeVersion, RuntimeInfo};
use notifications::{StorageNotifications, StorageEventStream};
use light::fetcher::ChangesProof;
use light::{call_executor::prove_execution, fetcher::ChangesProof};
use {cht, error, in_mem, block_builder::{self, api::BlockBuilder as BlockBuilderAPI}, genesis, consensus};
/// Type that implements `futures::Stream` of block import events.
@@ -318,7 +317,9 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
///
/// No changes are made.
pub fn execution_proof(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> error::Result<(Vec<u8>, Vec<Vec<u8>>)> {
self.state_at(id).and_then(|state| self.executor.prove_at_state(state, &mut Default::default(), method, call_data))
let state = self.state_at(id)?;
let header = self.prepare_environment_block(id)?;
prove_execution(state, header, &self.executor, method, call_data)
}
/// Reads given header and generates CHT-based header proof.
@@ -326,31 +327,6 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
self.header_proof_with_cht_size(id, cht::SIZE)
}
pub(crate) fn call_at_state(
&self,
at: &BlockId<Block>,
function: &'static str,
args: Vec<u8>,
changes: &mut OverlayedChanges
) -> error::Result<Vec<u8>> {
let state = self.state_at(at)?;
let execution_manager = || match self.api_execution_strategy {
ExecutionStrategy::NativeWhenPossible => ExecutionManager::NativeWhenPossible,
ExecutionStrategy::AlwaysWasm => ExecutionManager::AlwaysWasm,
ExecutionStrategy::Both => ExecutionManager::Both(|wasm_result, native_result| {
warn!("Consensus error between wasm and native runtime execution at block {:?}", at);
warn!(" Function {:?}", function);
warn!(" Native result {:?}", native_result);
warn!(" Wasm result {:?}", wasm_result);
wasm_result
}),
};
self.executor.call_at_state(&state, changes, function, &args, execution_manager())
.map(|res| res.0)
}
/// Get block hash by number.
pub fn block_hash(&self, block_number: <<Block as BlockT>::Header as HeaderT>::Number) -> error::Result<Option<Block::Hash>> {
self.backend.blockchain().hash(block_number)
@@ -359,7 +335,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
/// Reads given header and generates CHT-based header proof for CHT of given size.
pub fn header_proof_with_cht_size(&self, id: &BlockId<Block>, cht_size: u64) -> error::Result<(Block::Header, Vec<Vec<u8>>)> {
let proof_error = || error::ErrorKind::Backend(format!("Failed to generate header proof for {:?}", id));
let header = self.header(id)?.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", id)))?;
let header = self.backend.blockchain().expect_header(*id)?;
let block_num = *header.number();
let cht_num = cht::block_to_cht_number(cht_size, block_num).ok_or_else(proof_error)?;
let cht_start = cht::start_number(cht_size, cht_num);
@@ -383,13 +359,15 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
_ => return Err(error::ErrorKind::ChangesTriesNotSupported.into()),
};
let first_number = self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(first))?.as_();
let last_number = self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(last))?.as_();
key_changes::<_, Blake2Hasher>(
&config,
storage,
self.require_block_number_from_id(&BlockId::Hash(first))?.as_(),
first_number,
&ChangesTrieAnchorBlockId {
hash: convert_hash(&last),
number: self.require_block_number_from_id(&BlockId::Hash(last))?.as_(),
number: last_number,
},
self.backend.blockchain().info()?.best_number.as_(),
key)
@@ -465,7 +443,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
_ => return Err(error::ErrorKind::ChangesTriesNotSupported.into()),
};
let min_number = self.require_block_number_from_id(&BlockId::Hash(min))?;
let min_number = self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(min))?;
let recording_storage = AccessedRootsRecorder::<Block> {
storage,
min: min_number.as_(),
@@ -474,17 +452,19 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
let max_number = ::std::cmp::min(
self.backend.blockchain().info()?.best_number,
self.require_block_number_from_id(&BlockId::Hash(max))?,
self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(max))?,
);
// fetch key changes proof
let first_number = self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(first))?.as_();
let last_number = self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(last))?.as_();
let key_changes_proof = key_changes_proof::<_, Blake2Hasher>(
&config,
&recording_storage,
self.require_block_number_from_id(&BlockId::Hash(first))?.as_(),
first_number,
&ChangesTrieAnchorBlockId {
hash: convert_hash(&last),
number: self.require_block_number_from_id(&BlockId::Hash(last))?.as_(),
number: last_number,
},
max_number.as_(),
key
@@ -806,12 +786,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
/// while performing major synchronization work.
pub fn finalize_block(&self, id: BlockId<Block>, justification: Option<Justification>, notify: bool) -> error::Result<()> {
let last_best = self.backend.blockchain().info()?.best_hash;
let to_finalize_hash = match id {
BlockId::Hash(h) => h,
BlockId::Number(n) => self.backend.blockchain().hash(n)?
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("No block with number {:?}", n)))?,
};
let to_finalize_hash = self.backend.blockchain().expect_block_hash_from_id(&id)?;
self.apply_finality(to_finalize_hash, justification, last_best, notify)
}
@@ -845,12 +820,6 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
}
}
/// Convert an arbitrary block ID into a block hash, returning error if the block is unknown.
fn require_block_number_from_id(&self, id: &BlockId<Block>) -> error::Result<NumberFor<Block>> {
self.backend.blockchain().block_number_from_id(id)
.and_then(|n| n.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{}", id)).into()))
}
/// Get block header by id.
pub fn header(&self, id: &BlockId<Block>) -> error::Result<Option<<Block as BlockT>::Header>> {
self.backend.blockchain().header(*id)
@@ -992,6 +961,17 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
.map_err(|e| error::Error::from_state(Box::new(e)))?
.and_then(|c| Decode::decode(&mut &*c)))
}
/// Prepare in-memory header that is used in execution environment.
fn prepare_environment_block(&self, parent: &BlockId<Block>) -> error::Result<Block::Header> {
Ok(<<Block as BlockT>::Header as HeaderT>::new(
self.backend.blockchain().expect_block_number_from_id(parent)? + As::sa(1),
Default::default(),
Default::default(),
self.backend.blockchain().expect_block_hash_from_id(&parent)?,
Default::default(),
))
}
}
impl<B, E, Block, RA> ChainHeaderBackend<Block> for Client<B, E, Block, RA> where
@@ -1049,25 +1029,20 @@ impl<B, E, Block, RA> CallRuntimeAt<Block> for Client<B, E, Block, RA> where
changes: &mut OverlayedChanges,
initialised_block: &mut Option<BlockId<Block>>,
) -> error::Result<Vec<u8>> {
//TODO: Find a better way to prevent double block initialization
if function != "Core_initialise_block"
&& initialised_block.map(|id| id != *at).unwrap_or(true) {
let parent = at;
let header = <<Block as BlockT>::Header as HeaderT>::new(
self.block_number_from_id(parent)?
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", parent)))?
+ As::sa(1),
Default::default(),
Default::default(),
self.block_hash_from_id(&parent)?
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", parent)))?,
Default::default()
);
self.call_at_state(at, "Core_initialise_block", header.encode(), changes)?;
*initialised_block = Some(*at);
}
let execution_manager = match self.api_execution_strategy {
ExecutionStrategy::NativeWhenPossible => ExecutionManager::NativeWhenPossible,
ExecutionStrategy::AlwaysWasm => ExecutionManager::AlwaysWasm,
ExecutionStrategy::Both => ExecutionManager::Both(|wasm_result, native_result| {
warn!("Consensus error between wasm and native runtime execution at block {:?}", at);
warn!(" Function {:?}", function);
warn!(" Native result {:?}", native_result);
warn!(" Wasm result {:?}", wasm_result);
wasm_result
}),
};
self.call_at_state(at, function, args, changes)
self.executor.contextual_call(at, function, &args, changes, initialised_block,
|| self.prepare_environment_block(at), execution_manager)
}
fn runtime_version_at(&self, at: &BlockId<Block>) -> error::Result<RuntimeVersion> {
+154 -38
View File
@@ -17,15 +17,17 @@
//! Light client call exector. Executes methods on remote full nodes, fetching
//! execution proof and checking it locally.
use std::collections::HashSet;
use std::marker::PhantomData;
use std::sync::Arc;
use futures::{IntoFuture, Future};
use primitives::convert_hash;
use codec::Encode;
use primitives::{H256, Blake2Hasher, convert_hash};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
use state_machine::{Backend as StateBackend, CodeExecutor, OverlayedChanges,
execution_proof_check, ExecutionManager};
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT};
use state_machine::{self, Backend as StateBackend, CodeExecutor, OverlayedChanges,
create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager};
use hash_db::Hasher;
use blockchain::Backend as ChainBackend;
@@ -73,11 +75,7 @@ where
type Error = ClientError;
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> ClientResult<CallResult> {
let block_hash = match *id {
BlockId::Hash(hash) => hash,
BlockId::Number(number) => self.blockchain.hash(number)?
.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", number)))?,
};
let block_hash = self.blockchain.expect_block_hash_from_id(id)?;
let block_header = self.blockchain.expect_header(id.clone())?;
self.fetcher.remote_call(RemoteCallRequest {
@@ -89,6 +87,27 @@ where
}).into_future().wait()
}
fn contextual_call<
PB: Fn() -> ClientResult<Block::Header>,
EM: Fn(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
>(
&self,
at: &BlockId<Block>,
method: &str,
call_data: &[u8],
changes: &mut OverlayedChanges,
initialised_block: &mut Option<BlockId<Block>>,
_prepare_environment_block: PB,
_manager: ExecutionManager<EM>,
) -> ClientResult<Vec<u8>> where ExecutionManager<EM>: Clone {
// it is only possible to execute contextual call if changes are empty
if !changes.is_empty() || initialised_block.is_some() {
return Err(ClientErrorKind::NotAvailableOnLightClient.into());
}
self.call(at, method, call_data).map(|cr| cr.return_data)
}
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
let call_result = self.call(id, "version", &[])?;
RuntimeVersion::decode(&mut call_result.return_data.as_slice())
@@ -108,9 +127,9 @@ where
Err(ClientErrorKind::NotAvailableOnLightClient.into())
}
fn prove_at_state<S: StateBackend<H>>(
fn prove_at_trie_state<S: state_machine::TrieBackendStorage<H>>(
&self,
_state: S,
_state: &state_machine::TrieBackend<S, H>,
_changes: &mut OverlayedChanges,
_method: &str,
_call_data: &[u8]
@@ -123,7 +142,49 @@ where
}
}
/// Check remote execution proof using given backend.
/// Prove contextual execution using given block header in environment.
///
/// Method is executed using passed header as environment' current block.
/// Proof includes both environment preparation proof and method execution proof.
pub fn prove_execution<Block, S, E>(
state: S,
header: Block::Header,
executor: &E,
method: &str,
call_data: &[u8],
) -> ClientResult<(Vec<u8>, Vec<Vec<u8>>)>
where
Block: BlockT<Hash=H256>,
S: StateBackend<Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher>,
{
let trie_state = state.try_into_trie_backend()
.ok_or_else(|| Box::new(state_machine::ExecutionError::UnableToGenerateProof) as Box<state_machine::Error>)?;
// prepare execution environment + record preparation proof
let mut changes = Default::default();
let (_, init_proof) = executor.prove_at_trie_state(
&trie_state,
&mut changes,
"Core_initialise_block",
&header.encode(),
)?;
// execute method + record execution proof
let (result, exec_proof) = executor.prove_at_trie_state(&trie_state, &mut changes, method, call_data)?;
let total_proof = init_proof.into_iter()
.chain(exec_proof.into_iter())
.collect::<HashSet<_>>()
.into_iter()
.collect();
Ok((result, total_proof))
}
/// Check remote contextual execution proof using given backend.
///
/// Method is executed using passed header as environment' current block.
/// Proof shoul include both environment preparation proof and method execution proof.
pub fn check_execution_proof<Header, E, H>(
executor: &E,
request: &RemoteCallRequest<Header>,
@@ -134,54 +195,109 @@ pub fn check_execution_proof<Header, E, H>(
E: CodeExecutor<H>,
H: Hasher,
H::Out: Ord + HeapSizeOf,
{
let local_state_root = request.header.state_root();
let root: H::Out = convert_hash(&local_state_root);
// prepare execution environment + check preparation proof
let mut changes = OverlayedChanges::default();
let local_result = execution_proof_check::<H, _>(
root,
remote_proof,
let trie_backend = create_proof_check_backend(root, remote_proof)?;
let next_block = <Header as HeaderT>::new(
*request.header.number() + As::sa(1),
Default::default(),
Default::default(),
request.header.hash(),
Default::default(),
);
execution_proof_check_on_trie_backend::<H, _>(
&trie_backend,
&mut changes,
executor,
"Core_initialise_block",
&next_block.encode(),
)?;
// execute method
let local_result = execution_proof_check_on_trie_backend::<H, _>(
&trie_backend,
&mut changes,
executor,
&request.method,
&request.call_data)?;
&request.call_data,
)?;
Ok(CallResult { return_data: local_result, changes })
}
#[cfg(test)]
mod tests {
use test_client;
use consensus::BlockOrigin;
use test_client::{self, runtime::{Block, Header}, runtime::RuntimeApi, TestClient};
use executor::NativeExecutionDispatch;
use super::*;
#[test]
fn execution_proof_is_generated_and_checked() {
type TestClient = test_client::client::Client<
test_client::Backend,
test_client::Executor,
Block,
RuntimeApi
>;
fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec<u8>, Vec<u8>) {
let remote_block_id = BlockId::Number(at);
let remote_root = remote_client.state_at(&remote_block_id)
.unwrap().storage_root(::std::iter::empty()).0;
// 'fetch' execution proof from remote node
let (remote_result, remote_execution_proof) = remote_client.execution_proof(
&remote_block_id,
method,
&[]
).unwrap();
// check remote execution proof locally
let local_executor = test_client::LocalExecutor::new();
let local_result = check_execution_proof(&local_executor, &RemoteCallRequest {
block: test_client::runtime::Hash::default(),
header: test_client::runtime::Header {
state_root: remote_root.into(),
parent_hash: Default::default(),
number: at,
extrinsics_root: Default::default(),
digest: Default::default(),
},
method: method.into(),
call_data: vec![],
retry_count: None,
}, remote_execution_proof).unwrap();
(remote_result, local_result.return_data)
}
// prepare remote client
let remote_client = test_client::new();
let remote_block_id = BlockId::Number(0);
let remote_block_storage_root = remote_client.state_at(&remote_block_id)
.unwrap().storage_root(::std::iter::empty()).0;
for _ in 1..3 {
remote_client.import_justified(
BlockOrigin::Own,
remote_client.new_block().unwrap().bake().unwrap(),
Default::default(),
).unwrap();
}
// 'fetch' execution proof from remote node
let remote_execution_proof = remote_client.execution_proof(&remote_block_id, "Core_authorities", &[]).unwrap().1;
// check method that doesn't requires environment
let (remote, local) = execute(&remote_client, 0, "Core_authorities");
assert_eq!(remote, local);
// check remote execution proof locally
let local_executor = test_client::LocalExecutor::new();
check_execution_proof(&local_executor, &RemoteCallRequest {
block: test_client::runtime::Hash::default(),
header: test_client::runtime::Header {
state_root: remote_block_storage_root.into(),
parent_hash: Default::default(),
number: 0,
extrinsics_root: Default::default(),
digest: Default::default(),
},
method: "Core_authorities".into(),
call_data: vec![],
retry_count: None,
}, remote_execution_proof).unwrap();
// check method that requires environment
let (_, block) = execute(&remote_client, 0, "BlockBuilder_finalise_block");
let local_block: Header = Decode::decode(&mut &block[..]).unwrap();
assert_eq!(local_block.number, 1);
// check method that requires environment
let (_, block) = execute(&remote_client, 2, "BlockBuilder_finalise_block");
let local_block: Header = Decode::decode(&mut &block[..]).unwrap();
assert_eq!(local_block.number, 3);
}
}
+54 -15
View File
@@ -61,8 +61,8 @@ pub use changes_trie::{
key_changes, key_changes_proof, key_changes_proof_check,
prune as prune_changes_tries};
pub use overlayed_changes::OverlayedChanges;
pub use proving_backend::create_proof_check_backend_storage;
pub use trie_backend_essence::Storage;
pub use proving_backend::{create_proof_check_backend, create_proof_check_backend_storage};
pub use trie_backend_essence::{TrieBackendStorage, Storage};
pub use trie_backend::TrieBackend;
/// Default num of pages for the heap
@@ -191,6 +191,7 @@ pub enum ExecutionStrategy {
}
/// Like `ExecutionStrategy` only it also stores a handler in case of consensus failure.
#[derive(Clone)]
pub enum ExecutionManager<F> {
/// Execute with the native equivalent if it is compatible with the given wasm module; otherwise fall back to the wasm.
NativeWhenPossible,
@@ -381,14 +382,6 @@ where
}
/// Prove execution using the given state backend, overlayed changes, and call executor.
/// Produces a state-backend-specific "transaction" which can be used to apply the changes
/// to the backing store, such as the disk.
/// Execution proof is the set of all 'touched' storage DBValues from the backend.
///
/// On an error, no prospective changes are written to the overlay.
///
/// Note: changes to code will be in place if this call is made again. For running partial
/// blocks (e.g. a transaction at a time), ensure a different method is used.
pub fn prove_execution<B, H, Exec>(
backend: B,
overlay: &mut OverlayedChanges,
@@ -404,7 +397,32 @@ where
{
let trie_backend = backend.try_into_trie_backend()
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?;
let proving_backend = proving_backend::ProvingBackend::new(&trie_backend);
prove_execution_on_trie_backend(&trie_backend, overlay, exec, method, call_data)
}
/// Prove execution using the given trie backend, overlayed changes, and call executor.
/// Produces a state-backend-specific "transaction" which can be used to apply the changes
/// to the backing store, such as the disk.
/// Execution proof is the set of all 'touched' storage DBValues from the backend.
///
/// On an error, no prospective changes are written to the overlay.
///
/// Note: changes to code will be in place if this call is made again. For running partial
/// blocks (e.g. a transaction at a time), ensure a different method is used.
pub fn prove_execution_on_trie_backend<S, H, Exec>(
trie_backend: &TrieBackend<S, H>,
overlay: &mut OverlayedChanges,
exec: &Exec,
method: &str,
call_data: &[u8],
) -> Result<(Vec<u8>, Vec<Vec<u8>>), Box<Error>>
where
S: trie_backend_essence::TrieBackendStorage<H>,
H: Hasher,
Exec: CodeExecutor<H>,
H::Out: Ord + HeapSizeOf,
{
let proving_backend = proving_backend::ProvingBackend::new(trie_backend);
let (result, _, _) = execute::<H, _, changes_trie::InMemoryStorage<H>, _>(
&proving_backend,
None,
@@ -432,9 +450,31 @@ where
Exec: CodeExecutor<H>,
H::Out: Ord + HeapSizeOf,
{
let backend = proving_backend::create_proof_check_backend::<H>(root.into(), proof)?;
execute::<H, _, changes_trie::InMemoryStorage<H>, _>(&backend, None, overlay, exec, method, call_data, ExecutionStrategy::NativeWhenPossible)
.map(|(result, _, _)| result)
let trie_backend = proving_backend::create_proof_check_backend::<H>(root.into(), proof)?;
execution_proof_check_on_trie_backend(&trie_backend, overlay, exec, method, call_data)
}
/// Check execution proof on proving backend, generated by `prove_execution` call.
pub fn execution_proof_check_on_trie_backend<H, Exec>(
trie_backend: &TrieBackend<MemoryDB<H>, H>,
overlay: &mut OverlayedChanges,
exec: &Exec,
method: &str,
call_data: &[u8],
) -> Result<Vec<u8>, Box<Error>>
where
H: Hasher,
Exec: CodeExecutor<H>,
H::Out: Ord + HeapSizeOf,
{
execute::<H, _, changes_trie::InMemoryStorage<H>, _>(
trie_backend,
None, overlay,
exec,
method,
call_data,
ExecutionStrategy::NativeWhenPossible
).map(|(result, _, _)| result)
}
/// Generate storage read proof.
@@ -447,7 +487,6 @@ where
H: Hasher,
H::Out: Ord + HeapSizeOf
{
let trie_backend = backend.try_into_trie_backend()
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<Error>)?;
prove_read_on_trie_backend(&trie_backend, key)
@@ -82,6 +82,11 @@ impl OverlayedChangeSet {
}
impl OverlayedChanges {
/// Whether the overlayed changes are empty.
pub fn is_empty(&self) -> bool {
self.prospective.is_empty() && self.committed.is_empty()
}
/// Sets the changes trie configuration.
///
/// Returns false if configuration has been set already and we now trying
@@ -217,6 +217,7 @@ impl<'a,
/// Key-value pairs storage that is used by trie backend essence.
pub trait TrieBackendStorage<H: Hasher>: Send + Sync {
/// Get the value stored at key.
fn get(&self, key: &H::Out) -> Result<Option<DBValue>, String>;
}