Trie-based execution proof (#177)

* TrieBasedBackend

* trie tests

* redunant return_value removed

* use Trie::get_with to record trie proofs
This commit is contained in:
Svyatoslav Nikolsky
2018-06-21 16:23:48 +03:00
committed by Robert Habermeier
parent 504bcc0cae
commit 3d0bd2ce62
13 changed files with 685 additions and 219 deletions
+7
View File
@@ -2509,7 +2509,14 @@ name = "substrate-state-machine"
version = "0.1.0"
dependencies = [
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethereum-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hashdb 0.1.1 (git+https://github.com/paritytech/parity.git)",
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvdb 0.1.0 (git+https://github.com/paritytech/parity.git)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memorydb 0.1.1 (git+https://github.com/paritytech/parity.git)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"patricia-trie 0.1.0 (git+https://github.com/paritytech/parity.git)",
"substrate-primitives 0.1.0",
"triehash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
+8 -147
View File
@@ -17,13 +17,11 @@
//! Client backend that uses RocksDB database as storage. State is still kept in memory.
extern crate substrate_client as client;
extern crate ethereum_types;
extern crate kvdb_rocksdb;
extern crate kvdb;
extern crate hashdb;
extern crate memorydb;
extern crate parking_lot;
extern crate patricia_trie;
extern crate substrate_state_machine as state_machine;
extern crate substrate_primitives as primitives;
extern crate substrate_runtime_support as runtime_support;
@@ -38,22 +36,22 @@ extern crate kvdb_memorydb;
use std::sync::Arc;
use std::path::PathBuf;
use std::collections::HashMap;
use codec::Slicable;
use ethereum_types::H256 as TrieH256;
use hashdb::{DBValue, HashDB};
use hashdb::DBValue;
use kvdb_rocksdb::{Database, DatabaseConfig};
use kvdb::{KeyValueDB, DBTransaction};
use memorydb::MemoryDB;
use parking_lot::RwLock;
use patricia_trie::{TrieDB, TrieDBMut, TrieError, Trie, TrieMut};
use runtime_primitives::generic::BlockId;
use runtime_primitives::bft::Justification;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, Hashing, HashingFor, Zero};
use state_machine::backend::Backend as StateBackend;
use state_machine::CodeExecutor;
/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
pub type DbState = state_machine::TrieBackend;
/// Database settings.
pub struct DatabaseSettings {
/// Cache size in bytes. If `None` default is used.
@@ -300,133 +298,6 @@ impl<Block: BlockT> client::backend::BlockImportOperation<Block> for BlockImport
}
}
struct Ephemeral<'a> {
backing: &'a KeyValueDB,
overlay: &'a mut MemoryDB,
}
impl<'a> HashDB for Ephemeral<'a> {
fn keys(&self) -> HashMap<TrieH256, i32> {
self.overlay.keys() // TODO: iterate backing
}
fn get(&self, key: &TrieH256) -> Option<DBValue> {
match self.overlay.raw(key) {
Some((val, i)) => {
if i <= 0 {
None
} else {
Some(val)
}
}
None => {
match self.backing.get(::columns::STATE, &key.0[..]) {
Ok(x) => x,
Err(e) => {
warn!("Failed to read from DB: {}", e);
None
}
}
}
}
}
fn contains(&self, key: &TrieH256) -> bool {
self.get(key).is_some()
}
fn insert(&mut self, value: &[u8]) -> TrieH256 {
self.overlay.insert(value)
}
fn emplace(&mut self, key: TrieH256, value: DBValue) {
self.overlay.emplace(key, value)
}
fn remove(&mut self, key: &TrieH256) {
self.overlay.remove(key)
}
}
/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
#[derive(Clone)]
pub struct DbState {
db: Arc<KeyValueDB>,
root: TrieH256,
}
impl state_machine::Backend for DbState {
type Error = client::error::Error;
type Transaction = MemoryDB;
fn storage(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
let mut read_overlay = MemoryDB::default();
let eph = Ephemeral {
backing: &*self.db,
overlay: &mut read_overlay,
};
let map_e = |e: Box<TrieError>| ::client::error::Error::from(format!("Trie lookup error: {}", e));
TrieDB::new(&eph, &self.root).map_err(map_e)?
.get(key).map(|x| x.map(|val| val.to_vec())).map_err(map_e)
}
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
let mut read_overlay = MemoryDB::default();
let eph = Ephemeral {
backing: &*self.db,
overlay: &mut read_overlay,
};
let collect_all = || -> Result<_, Box<TrieError>> {
let trie = TrieDB::new(&eph, &self.root)?;
let mut v = Vec::new();
for x in trie.iter()? {
let (key, value) = x?;
v.push((key.to_vec(), value.to_vec()));
}
Ok(v)
};
match collect_all() {
Ok(v) => v,
Err(e) => {
debug!("Error extracting trie values: {}", e);
Vec::new()
}
}
}
fn storage_root<I>(&self, delta: I) -> ([u8; 32], MemoryDB)
where I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
{
let mut write_overlay = MemoryDB::default();
let mut root = self.root;
{
let mut eph = Ephemeral {
backing: &*self.db,
overlay: &mut write_overlay,
};
let mut trie = TrieDBMut::from_existing(&mut eph, &mut root).expect("prior state root to exist"); // TODO: handle gracefully
for (key, change) in delta {
let result = match change {
Some(val) => trie.insert(&key, &val),
None => trie.remove(&key), // TODO: archive mode
};
if let Err(e) = result {
warn!("Failed to write to trie: {}", e);
}
}
}
(root.0.into(), write_overlay)
}
}
/// Disk backend. Keeps data in a key-value store. In archive mode, trie nodes are kept from all blocks.
/// Otherwise, trie nodes are kept only from the most recent block.
pub struct Backend<Block: BlockT> {
@@ -522,25 +393,14 @@ impl<Block: BlockT> client::backend::Backend<Block> for Backend<Block> where
// special case for genesis initialization
match block {
BlockId::Hash(h) if h == Default::default() => {
let mut root = TrieH256::default();
let mut db = MemoryDB::default();
TrieDBMut::new(&mut db, &mut root);
return Ok(DbState {
db: self.db.clone(),
root,
})
}
BlockId::Hash(h) if h == Default::default() =>
return Ok(DbState::with_kvdb_for_genesis(self.db.clone(), ::columns::STATE)),
_ => {}
}
self.blockchain.header(block).and_then(|maybe_hdr| maybe_hdr.map(|hdr| {
let root: [u8; 32] = hdr.state_root().clone().into();
DbState {
db: self.db.clone(),
root: root.into(),
}
DbState::with_kvdb(self.db.clone(), ::columns::STATE, root.into())
}).ok_or_else(|| client::error::ErrorKind::UnknownBlock(format!("{:?}", block)).into()))
}
}
@@ -552,6 +412,7 @@ impl<Block: BlockT> client::backend::LocalBackend<Block> for Backend<Block> wher
#[cfg(test)]
mod tests {
use hashdb::HashDB;
use super::*;
use client::backend::Backend as BTrait;
use client::backend::BlockImportOperation as Op;
+63 -46
View File
@@ -17,9 +17,8 @@
use std::sync::Arc;
use futures::{IntoFuture, Future};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::Block as BlockT;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
use state_machine::{self, OverlayedChanges, Backend as StateBackend, CodeExecutor};
use state_machine::backend::InMemory as InMemoryStateBackend;
use backend;
use blockchain::Backend as ChainBackend;
@@ -49,6 +48,11 @@ pub trait CallExecutor<B: BlockT> {
///
/// No changes are made.
fn call_at_state<S: state_machine::Backend>(&self, state: &S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, S::Transaction), error::Error>;
/// 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>(&self, state: S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error>;
}
/// Call executor that executes methods locally, querying all required
@@ -105,6 +109,18 @@ impl<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E>
call_data,
).map_err(Into::into)
}
fn prove_at_state<S: state_machine::Backend>(&self, state: S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> {
state_machine::prove(
state,
changes,
&self.executor,
method,
call_data,
)
.map(|(result, proof, _)| (result, proof))
.map_err(Into::into)
}
}
impl<B, F> RemoteCallExecutor<B, F> {
@@ -140,69 +156,70 @@ impl<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F>
fn call_at_state<S: state_machine::Backend>(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> error::Result<(Vec<u8>, S::Transaction)> {
Err(error::ErrorKind::NotAvailableOnLightClient.into())
}
fn prove_at_state<S: state_machine::Backend>(&self, _state: S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> {
Err(error::ErrorKind::NotAvailableOnLightClient.into())
}
}
/// Check remote execution proof.
pub fn check_execution_proof<B, E, Block>(backend: &B, executor: &E, request: &RemoteCallRequest<Block::Hash>, remote_proof: (Vec<u8>, Vec<Vec<u8>>)) -> Result<CallResult, error::Error>
/// Check remote execution proof using given backend.
pub fn check_execution_proof<B, E, Block>(backend: &B, executor: &E, request: &RemoteCallRequest<Block::Hash>, remote_proof: Vec<Vec<u8>>) -> Result<CallResult, error::Error>
where
B: backend::RemoteBackend<Block>,
E: CodeExecutor,
Block: BlockT,
<<Block as BlockT>::Header as HeaderT>::Hash: Into<[u8; 32]>, // TODO: remove when patricia_trie generic.
error::Error: From<<<B as backend::Backend<Block>>::State as StateBackend>::Error>,
{
use runtime_primitives::traits::{Header, Hashing, HashingFor};
let (remote_result, remote_proof) = remote_proof;
let remote_state = state_from_execution_proof(remote_proof);
let remote_state_root = HashingFor::<Block>::trie_root(remote_state.pairs().into_iter());
let local_header = backend.blockchain().header(BlockId::Hash(request.block))?;
let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", request.block)))?;
let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{}", request.block)))?;
let local_state_root = local_header.state_root().clone();
do_check_execution_proof(local_state_root, executor, request, remote_proof)
}
if remote_state_root != local_state_root {
return Err(error::ErrorKind::InvalidExecutionProof.into());
}
/// Check remote execution proof using given state root.
fn do_check_execution_proof<E, H>(local_state_root: H, executor: &E, request: &RemoteCallRequest<H>, remote_proof: Vec<Vec<u8>>) -> Result<CallResult, error::Error>
where
E: CodeExecutor,
H: Into<[u8; 32]>, // TODO: remove when patricia_trie generic.
{
let mut changes = OverlayedChanges::default();
let (local_result, _) = state_machine::execute(
&remote_state,
let (local_result, _) = state_machine::proof_check(
local_state_root.into(),
remote_proof,
&mut changes,
executor,
&request.method,
&request.call_data,
)?;
if local_result != remote_result {
return Err(error::ErrorKind::InvalidExecutionProof.into());
}
&request.call_data)?;
Ok(CallResult { return_data: local_result, changes })
}
/// Convert state to execution proof. Proof is simple the whole state (temporary).
// TODO [light]: this method must be removed after trie-based proofs are landed.
pub fn state_to_execution_proof<B: state_machine::Backend>(state: &B) -> Vec<Vec<u8>> {
state.pairs().into_iter()
.flat_map(|(k, v)| ::std::iter::once(k).chain(::std::iter::once(v)))
.collect()
}
#[cfg(test)]
mod tests {
use runtime_primitives::generic::BlockId;
use state_machine::Backend;
use test_client;
use light::RemoteCallRequest;
use super::do_check_execution_proof;
/// Convert execution proof to in-memory state for check. Reverse function for state_to_execution_proof.
// TODO [light]: this method must be removed after trie-based proofs are landed.
fn state_from_execution_proof(proof: Vec<Vec<u8>>) -> InMemoryStateBackend {
let mut changes = Vec::new();
let mut proof_iter = proof.into_iter();
loop {
let key = proof_iter.next();
let value = proof_iter.next();
if let (Some(key), Some(value)) = (key, value) {
changes.push((key, Some(value)));
} else {
break;
}
#[test]
fn execution_proof_is_generated_and_checked() {
// 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;
// 'fetch' execution proof from remote node
let remote_execution_proof = remote_client.execution_proof(&remote_block_id, "authorities", &[]).unwrap().1;
// check remote execution proof locally
let local_executor = test_client::NativeExecutor::new();
do_check_execution_proof(remote_block_storage_root, &local_executor, &RemoteCallRequest {
block: Default::default(),
method: "authorities".into(),
call_data: vec![],
}, remote_execution_proof).unwrap();
}
InMemoryStateBackend::default().update(changes)
}
+1 -6
View File
@@ -232,12 +232,7 @@ impl<B, E, Block: BlockT> Client<B, E, Block> 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>>)> {
use call_executor::state_to_execution_proof;
let result = self.executor.call(id, method, call_data);
let result = result?.return_data;
let proof = self.backend.state_at(*id).map(|state| state_to_execution_proof(&state))?;
Ok((result, proof))
self.state_at(id).and_then(|state| self.executor.prove_at_state(state, &mut Default::default(), method, call_data))
}
/// Set up the native execution environment to call into a native runtime code.
+11 -3
View File
@@ -19,7 +19,8 @@
use std::sync::Arc;
use futures::future::IntoFuture;
use state_machine::CodeExecutor;
use state_machine::{CodeExecutor, TryIntoTrieBackend as TryIntoStateTrieBackend,
TrieBackend as StateTrieBackend};
use state_machine::backend::Backend as StateBackend;
use runtime_primitives::generic::BlockId;
use runtime_primitives::bft::Justification;
@@ -54,7 +55,7 @@ pub trait Fetcher<B: BlockT>: Send + Sync {
/// Light client remote data checker.
pub trait FetchChecker<B: BlockT>: Send + Sync {
/// Check remote method execution proof.
fn check_execution_proof(&self, request: &RemoteCallRequest<B::Hash>, remote_proof: (Vec<u8>, Vec<Vec<u8>>)) -> error::Result<CallResult>;
fn check_execution_proof(&self, request: &RemoteCallRequest<B::Hash>, remote_proof: Vec<Vec<u8>>) -> error::Result<CallResult>;
}
/// Light client backend.
@@ -202,12 +203,19 @@ impl<H: Clone> StateBackend for OnDemandState<H> {
}
}
impl<H> TryIntoStateTrieBackend for OnDemandState<H> {
fn try_into_trie_backend(self) -> Option<StateTrieBackend> {
None
}
}
impl<E, B> FetchChecker<B> for LightDataChecker<E, B>
where
E: CodeExecutor,
B: BlockT,
<<B as BlockT>::Header as HeaderT>::Hash: Into<[u8; 32]>, // TODO: remove when patricia_trie generic.
{
fn check_execution_proof(&self, request: &RemoteCallRequest<B::Hash>, remote_proof: (Vec<u8>, Vec<Vec<u8>>)) -> error::Result<CallResult> {
fn check_execution_proof(&self, request: &RemoteCallRequest<B::Hash>, remote_proof: Vec<Vec<u8>>) -> error::Result<CallResult> {
check_execution_proof(&*self.backend, &self.executor, request, remote_proof)
}
}
@@ -162,8 +162,6 @@ pub enum Direction {
pub struct RemoteCallResponse {
/// Id of a request this response was made for.
pub id: RequestId,
/// Method return value.
pub value: Vec<u8>,
/// Execution proof.
pub proof: Vec<Vec<u8>>,
}
+4 -5
View File
@@ -160,7 +160,7 @@ impl<B, E> OnDemandService for OnDemand<B, E> where
fn on_remote_response(&self, io: &mut SyncIo, peer: PeerId, response: message::RemoteCallResponse) {
let mut core = self.core.lock();
match core.remove(peer, response.id) {
Some(request) => match self.checker.check_execution_proof(&request.request, (response.value, response.proof)) {
Some(request) => match self.checker.check_execution_proof(&request.request, response.proof) {
Ok(response) => {
// we do not bother if receiver has been dropped already
let _ = request.sender.send(response);
@@ -312,10 +312,10 @@ mod tests {
}
impl FetchChecker<Block> for DummyFetchChecker {
fn check_execution_proof(&self, _request: &RemoteCallRequest<Hash>, remote_proof: (Vec<u8>, Vec<Vec<u8>>)) -> client::error::Result<client::CallResult> {
fn check_execution_proof(&self, _request: &RemoteCallRequest<Hash>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<client::CallResult> {
match self.ok {
true => Ok(client::CallResult {
return_data: remote_proof.0,
return_data: vec![42],
changes: Default::default(),
}),
false => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
@@ -338,7 +338,6 @@ mod tests {
fn receive_response(on_demand: &OnDemand<Block, DummyExecutor>, network: &mut TestIo, peer: PeerId, id: message::RequestId) {
on_demand.on_remote_response(network, peer, message::RemoteCallResponse {
id: id,
value: vec![1],
proof: vec![vec![2]],
});
}
@@ -431,7 +430,7 @@ mod tests {
let response = on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] });
let thread = ::std::thread::spawn(move || {
let result = response.wait().unwrap();
assert_eq!(result.return_data, vec![1]);
assert_eq!(result.return_data, vec![42]);
});
receive_response(&*on_demand, &mut network, 0, 0);
+4 -4
View File
@@ -507,17 +507,17 @@ impl<B: BlockT> Protocol<B> where
fn on_remote_call_request(&self, io: &mut SyncIo, peer_id: PeerId, request: message::RemoteCallRequest<B::Hash>) {
trace!(target: "sync", "Remote request {} from {} ({} at {})", request.id, peer_id, request.method, request.block);
let (value, proof) = match self.chain.execution_proof(&request.block, &request.method, &request.data) {
Ok((value, proof)) => (value, proof),
let proof = match self.chain.execution_proof(&request.block, &request.method, &request.data) {
Ok((_, proof)) => proof,
Err(error) => {
trace!(target: "sync", "Remote request {} from {} ({} at {}) failed with: {}",
request.id, peer_id, request.method, request.block, error);
(Default::default(), Default::default())
Default::default()
},
};
self.send_message(io, peer_id, GenericMessage::RemoteCallResponse(message::RemoteCallResponse {
id: request.id, value, proof,
id: request.id, proof,
}));
}
+11 -2
View File
@@ -5,7 +5,16 @@ authors = ["Parity Technologies <admin@parity.io>"]
description = "Substrate State Machine"
[dependencies]
substrate-primitives = { path = "../primitives", version = "0.1.0" }
triehash = "0.1.2"
byteorder = "1.1"
ethereum-types = "0.3"
hex-literal = "0.1.0"
log = "0.3"
parking_lot = "0.4"
triehash = "0.1"
substrate-primitives = { path = "../primitives", version = "0.1.0" }
hashdb = { git = "https://github.com/paritytech/parity.git" }
kvdb = { git = "https://github.com/paritytech/parity.git" }
memorydb = { git = "https://github.com/paritytech/parity.git" }
patricia-trie = { git = "https://github.com/paritytech/parity.git" }
@@ -19,12 +19,13 @@
use std::{error, fmt};
use std::collections::HashMap;
use std::sync::Arc;
use trie_backend::{TryIntoTrieBackend, TrieBackend};
/// A state backend is used to read state data and can have changes committed
/// to it.
///
/// The clone operation should be cheap.
pub trait Backend: Clone {
/// The clone operation (if implemented) should be cheap.
pub trait Backend: TryIntoTrieBackend {
/// An error type when fetching data is not possible.
type Error: super::Error;
@@ -124,3 +125,24 @@ impl Backend for InMemory {
}
}
impl TryIntoTrieBackend for InMemory {
fn try_into_trie_backend(self) -> Option<TrieBackend> {
use ethereum_types::H256 as TrieH256;
use memorydb::MemoryDB;
use patricia_trie::{TrieDBMut, TrieMut};
let mut root = TrieH256::default();
let mut mdb = MemoryDB::default();
{
let mut trie = TrieDBMut::new(&mut mdb, &mut root);
for (key, value) in self.inner.iter() {
if let Err(e) = trie.insert(&key, &value) {
warn!(target: "trie", "Failed to write to trie: {}", e);
return None;
}
}
}
Some(TrieBackend::with_memorydb(mdb, root))
}
}
+97 -2
View File
@@ -20,8 +20,19 @@
#[cfg_attr(test, macro_use)]
extern crate hex_literal;
#[macro_use]
extern crate log;
extern crate ethereum_types;
extern crate kvdb;
extern crate hashdb;
extern crate memorydb;
extern crate triehash;
extern crate patricia_trie;
extern crate byteorder;
extern crate parking_lot;
use std::collections::HashMap;
use std::collections::hash_map::Drain;
@@ -30,10 +41,13 @@ use std::fmt;
pub mod backend;
mod ext;
mod testing;
mod proving_backend;
mod trie_backend;
pub use testing::TestExternalities;
pub use ext::Ext;
pub use backend::Backend;
pub use trie_backend::{TryIntoTrieBackend, TrieBackend};
/// The overlayed changes to state to be queried on top of the backend.
///
@@ -93,7 +107,11 @@ impl<E> Error for E where E: 'static + fmt::Debug + fmt::Display + Send {}
#[derive(Debug, Eq, PartialEq)]
pub enum ExecutionError {
/// The entry `:code` doesn't exist in storage so there's no way we can execute anything.
CodeEntryDoesNotExist
CodeEntryDoesNotExist,
/// Backend is incompatible with execution proof generation process.
UnableToGenerateProof,
/// Invalid execution proof.
InvalidProof,
}
impl fmt::Display for ExecutionError {
@@ -156,7 +174,6 @@ pub fn execute<B: backend::Backend, Exec: CodeExecutor>(
call_data: &[u8],
) -> Result<(Vec<u8>, B::Transaction), Box<Error>>
{
let result = {
let mut externalities = ext::Ext::new(overlay, backend);
// make a copy.
@@ -184,12 +201,67 @@ pub fn execute<B: backend::Backend, Exec: CodeExecutor>(
}
}
/// 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<B: TryIntoTrieBackend, Exec: CodeExecutor>(
backend: B,
overlay: &mut OverlayedChanges,
exec: &Exec,
method: &str,
call_data: &[u8],
) -> Result<(Vec<u8>, Vec<Vec<u8>>, <TrieBackend as Backend>::Transaction), Box<Error>>
{
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);
let (result, transaction) = execute(&proving_backend, overlay, exec, method, call_data)?;
let proof = proving_backend.extract_proof();
Ok((result, proof, transaction))
}
/// Check execution proof, generated by `prove` call.
pub fn proof_check<Exec: CodeExecutor>(
root: [u8; 32],
proof: Vec<Vec<u8>>,
overlay: &mut OverlayedChanges,
exec: &Exec,
method: &str,
call_data: &[u8],
) -> Result<(Vec<u8>, memorydb::MemoryDB), Box<Error>>
{
let backend = proving_backend::create_proof_check_backend(root.into(), proof)?;
execute(&backend, overlay, exec, method, call_data)
}
#[cfg(test)]
mod tests {
use super::*;
use super::backend::InMemory;
use super::ext::Ext;
struct DummyCodeExecutor;
impl CodeExecutor for DummyCodeExecutor {
type Error = u8;
fn call<E: Externalities>(
&self,
ext: &mut E,
_code: &[u8],
_method: &str,
_data: &[u8],
) -> Result<Vec<u8>, Self::Error> {
Ok(vec![ext.storage(b"value1").unwrap()[0] + ext.storage(b"value2").unwrap()[0]])
}
}
#[test]
fn overlayed_storage_works() {
let mut overlayed = OverlayedChanges::default();
@@ -248,4 +320,27 @@ mod tests {
const ROOT: [u8; 32] = hex!("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3");
assert_eq!(ext.storage_root(), ROOT);
}
#[test]
fn execute_works() {
assert_eq!(execute(&trie_backend::tests::test_trie(),
&mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap().0, vec![66]);
}
#[test]
fn prove_and_proof_check_works() {
// fetch execution proof from 'remote' full node
let remote_backend = trie_backend::tests::test_trie();
let remote_root = remote_backend.storage_root(::std::iter::empty()).0;
let (remote_result, remote_proof, _) = prove(remote_backend,
&mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap();
// check proof locally
let (local_result, _) = proof_check(remote_root, remote_proof,
&mut Default::default(), &DummyCodeExecutor, "test", &[]).unwrap();
// check that both results are correct
assert_eq!(remote_result, vec![66]);
assert_eq!(remote_result, local_result);
}
}
@@ -0,0 +1,165 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Proving state machine backend.
use std::cell::RefCell;
use ethereum_types::H256 as TrieH256;
use hashdb::HashDB;
use memorydb::MemoryDB;
use patricia_trie::{TrieDB, TrieError, Trie, Recorder};
use trie_backend::{TrieBackend, Ephemeral};
use {Error, ExecutionError, Backend, TryIntoTrieBackend};
/// Patricia trie-based backend which also tracks all touched storage trie values.
/// These can be sent to remote node and used as a proof of execution.
pub struct ProvingBackend {
backend: TrieBackend,
proof_recorder: RefCell<Recorder>,
}
impl ProvingBackend {
/// Create new proving backend.
pub fn new(backend: TrieBackend) -> Self {
ProvingBackend {
backend,
proof_recorder: RefCell::new(Recorder::new()),
}
}
/// Consume the backend, extracting the gathered proof in lexicographical order
/// by value.
pub fn extract_proof(self) -> Vec<Vec<u8>> {
self.proof_recorder.into_inner().drain()
.into_iter()
.map(|n| n.data.to_vec())
.collect()
}
}
impl Backend for ProvingBackend {
type Error = String;
type Transaction = MemoryDB;
fn storage(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
let mut read_overlay = MemoryDB::default();
let eph = Ephemeral::new(
self.backend.backend_storage(),
&mut read_overlay,
);
let map_e = |e: Box<TrieError>| format!("Trie lookup error: {}", e);
let mut proof_recorder = self.proof_recorder.try_borrow_mut()
.expect("only fails when already borrowed; storage() is non-reentrant; qed");
TrieDB::new(&eph, &self.backend.root()).map_err(map_e)?
.get_with(key, &mut *proof_recorder).map(|x| x.map(|val| val.to_vec())).map_err(map_e)
}
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
self.backend.pairs()
}
fn storage_root<I>(&self, delta: I) -> ([u8; 32], MemoryDB)
where I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
{
self.backend.storage_root(delta)
}
}
impl TryIntoTrieBackend for ProvingBackend {
fn try_into_trie_backend(self) -> Option<TrieBackend> {
None
}
}
/// Create proof check backend.
pub fn create_proof_check_backend(root: TrieH256, proof: Vec<Vec<u8>>) -> Result<TrieBackend, Box<Error>> {
let mut db = MemoryDB::new();
for item in proof {
db.insert(&item);
}
if !db.contains(&root) {
return Err(Box::new(ExecutionError::InvalidProof) as Box<Error>);
}
Ok(TrieBackend::with_memorydb(db, root))
}
#[cfg(test)]
mod tests {
use backend::{InMemory};
use trie_backend::tests::test_trie;
use super::*;
fn test_proving() -> ProvingBackend {
ProvingBackend::new(test_trie())
}
#[test]
fn proof_is_empty_until_value_is_read() {
assert!(test_proving().extract_proof().is_empty());
}
#[test]
fn proof_is_non_empty_after_value_is_read() {
let backend = test_proving();
assert_eq!(backend.storage(b"key").unwrap(), Some(b"value".to_vec()));
assert!(!backend.extract_proof().is_empty());
}
#[test]
fn proof_is_invalid_when_does_not_contains_root() {
assert!(create_proof_check_backend(1.into(), vec![]).is_err());
}
#[test]
fn passes_throgh_backend_calls() {
let trie_backend = test_trie();
let proving_backend = test_proving();
assert_eq!(trie_backend.storage(b"key").unwrap(), proving_backend.storage(b"key").unwrap());
assert_eq!(trie_backend.pairs(), proving_backend.pairs());
let (trie_root, mut trie_mdb) = trie_backend.storage_root(::std::iter::empty());
let (proving_root, mut proving_mdb) = proving_backend.storage_root(::std::iter::empty());
assert_eq!(trie_root, proving_root);
assert_eq!(trie_mdb.drain(), proving_mdb.drain());
}
#[test]
fn proof_recorded_and_checked() {
let contents = (0..64).map(|i| (vec![i], Some(vec![i]))).collect::<Vec<_>>();
let in_memory = InMemory::default();
let in_memory = in_memory.update(contents);
let in_memory_root = in_memory.storage_root(::std::iter::empty()).0;
(0..64).for_each(|i| assert_eq!(in_memory.storage(&[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(in_memory_root.into(), proof).unwrap();
assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]);
}
}
@@ -0,0 +1,290 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Trie-based state machine backend.
use std::collections::HashMap;
use std::sync::Arc;
use ethereum_types::H256 as TrieH256;
use hashdb::{DBValue, HashDB};
use kvdb::KeyValueDB;
use memorydb::MemoryDB;
use patricia_trie::{TrieDB, TrieDBMut, TrieError, Trie, TrieMut};
use {Backend};
/// Try convert into trie-based backend.
pub trait TryIntoTrieBackend {
/// Try to convert self into trie backend.
fn try_into_trie_backend(self) -> Option<TrieBackend>;
}
/// Patricia trie-based backend. Transaction type is an overlay of changes to commit.
#[derive(Clone)]
pub struct TrieBackend {
storage: TrieBackendStorage,
root: TrieH256,
}
impl TrieBackend {
/// Create new trie-based backend.
pub fn with_kvdb(db: Arc<KeyValueDB>, storage_column: Option<u32>, root: TrieH256) -> Self {
TrieBackend {
storage: TrieBackendStorage::KeyValueDb(db, storage_column),
root,
}
}
/// Create new trie-based backend for genesis block.
pub fn with_kvdb_for_genesis(db: Arc<KeyValueDB>, storage_column: Option<u32>) -> Self {
let mut root = TrieH256::default();
let mut mdb = MemoryDB::default();
TrieDBMut::new(&mut mdb, &mut root);
Self::with_kvdb(db, storage_column, root)
}
/// Create new trie-based backend backed by MemoryDb storage.
pub fn with_memorydb(db: MemoryDB, root: TrieH256) -> Self {
// TODO: check that root is a part of db???
TrieBackend {
storage: TrieBackendStorage::MemoryDb(db),
root,
}
}
/// Get backend storage reference.
pub fn backend_storage(&self) -> &TrieBackendStorage {
&self.storage
}
/// Get trie root.
pub fn root(&self) -> &TrieH256 {
&self.root
}
}
impl Backend for TrieBackend {
type Error = String;
type Transaction = MemoryDB;
fn storage(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
let mut read_overlay = MemoryDB::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
let map_e = |e: Box<TrieError>| format!("Trie lookup error: {}", e);
TrieDB::new(&eph, &self.root).map_err(map_e)?
.get(key).map(|x| x.map(|val| val.to_vec())).map_err(map_e)
}
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
let mut read_overlay = MemoryDB::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
let collect_all = || -> Result<_, Box<TrieError>> {
let trie = TrieDB::new(&eph, &self.root)?;
let mut v = Vec::new();
for x in trie.iter()? {
let (key, value) = x?;
v.push((key.to_vec(), value.to_vec()));
}
Ok(v)
};
match collect_all() {
Ok(v) => v,
Err(e) => {
debug!(target: "trie", "Error extracting trie values: {}", e);
Vec::new()
}
}
}
fn storage_root<I>(&self, delta: I) -> ([u8; 32], MemoryDB)
where I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
{
let mut write_overlay = MemoryDB::default();
let mut root = self.root;
{
let mut eph = Ephemeral {
storage: &self.storage,
overlay: &mut write_overlay,
};
let mut trie = TrieDBMut::from_existing(&mut eph, &mut root).expect("prior state root to exist"); // TODO: handle gracefully
for (key, change) in delta {
let result = match change {
Some(val) => trie.insert(&key, &val),
None => trie.remove(&key), // TODO: archive mode
};
if let Err(e) = result {
warn!(target: "trie", "Failed to write to trie: {}", e);
}
}
}
(root.0.into(), write_overlay)
}
}
impl TryIntoTrieBackend for TrieBackend {
fn try_into_trie_backend(self) -> Option<TrieBackend> {
Some(self)
}
}
pub struct Ephemeral<'a> {
storage: &'a TrieBackendStorage,
overlay: &'a mut MemoryDB,
}
impl<'a> Ephemeral<'a> {
pub fn new(storage: &'a TrieBackendStorage, overlay: &'a mut MemoryDB) -> Self {
Ephemeral {
storage,
overlay,
}
}
}
impl<'a> HashDB for Ephemeral<'a> {
fn keys(&self) -> HashMap<TrieH256, i32> {
self.overlay.keys() // TODO: iterate backing
}
fn get(&self, key: &TrieH256) -> Option<DBValue> {
match self.overlay.raw(key) {
Some((val, i)) => {
if i <= 0 {
None
} else {
Some(val)
}
}
None => match self.storage.get(&key.0[..]) {
Ok(x) => x,
Err(e) => {
warn!(target: "trie", "Failed to read from DB: {}", e);
None
},
},
}
}
fn contains(&self, key: &TrieH256) -> bool {
self.get(key).is_some()
}
fn insert(&mut self, value: &[u8]) -> TrieH256 {
self.overlay.insert(value)
}
fn emplace(&mut self, key: TrieH256, value: DBValue) {
self.overlay.emplace(key, value)
}
fn remove(&mut self, key: &TrieH256) {
self.overlay.remove(key)
}
}
#[derive(Clone)]
pub enum TrieBackendStorage {
/// Key value db + storage column.
KeyValueDb(Arc<KeyValueDB>, Option<u32>),
/// Hash db.
MemoryDb(MemoryDB),
}
impl TrieBackendStorage {
pub fn get(&self, key: &[u8]) -> Result<Option<DBValue>, String> {
match *self {
TrieBackendStorage::KeyValueDb(ref db, storage_column) =>
db.get(storage_column, key)
.map_err(|e| format!("Trie lookup error: {}", e)),
TrieBackendStorage::MemoryDb(ref db) =>
Ok(db.get(&TrieH256::from_slice(key))),
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
fn test_db() -> (MemoryDB, TrieH256) {
let mut root = TrieH256::default();
let mut mdb = MemoryDB::default();
{
let mut trie = TrieDBMut::new(&mut mdb, &mut root);
trie.insert(b"key", b"value").unwrap();
trie.insert(b"value1", &[42]).unwrap();
trie.insert(b"value2", &[24]).unwrap();
trie.insert(b":code", b"return 42").unwrap();
}
(mdb, root)
}
pub fn test_trie() -> TrieBackend {
let (mdb, root) = test_db();
TrieBackend::with_memorydb(mdb, root)
}
#[test]
fn read_from_storage_returns_some() {
assert_eq!(test_trie().storage(b"key").unwrap(), Some(b"value".to_vec()));
}
#[test]
fn read_from_storage_returns_none() {
assert_eq!(test_trie().storage(b"non-existing-key").unwrap(), None);
}
#[test]
fn pairs_are_not_empty_on_non_empty_storage() {
assert!(!test_trie().pairs().is_empty());
}
#[test]
fn pairs_are_empty_on_empty_storage() {
assert!(TrieBackend::with_memorydb(MemoryDB::new(), Default::default()).pairs().is_empty());
}
#[test]
fn storage_root_is_non_default() {
assert!(test_trie().storage_root(::std::iter::empty()).0 != [0; 32]);
}
#[test]
fn storage_root_transaction_is_empty() {
assert!(test_trie().storage_root(::std::iter::empty()).1.drain().is_empty());
}
#[test]
fn storage_root_transaction_is_non_empty() {
let (new_root, mut tx) = test_trie().storage_root(vec![(b"new-key".to_vec(), Some(b"new-value".to_vec()))]);
assert!(!tx.drain().is_empty());
assert!(new_root != test_trie().storage_root(::std::iter::empty()).0);
}
}