Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 84d7df8

Browse files
svyatonikgavofyork
authored andcommitted
Trie-based execution proof (#177)
* TrieBasedBackend * trie tests * redunant return_value removed * use Trie::get_with to record trie proofs
1 parent e475063 commit 84d7df8

File tree

13 files changed

+686
-220
lines changed

13 files changed

+686
-220
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

substrate/client/db/src/lib.rs

Lines changed: 8 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717
//! Client backend that uses RocksDB database as storage. State is still kept in memory.
1818
1919
extern crate substrate_client as client;
20-
extern crate ethereum_types;
2120
extern crate kvdb_rocksdb;
2221
extern crate kvdb;
2322
extern crate hashdb;
2423
extern crate memorydb;
2524
extern crate parking_lot;
26-
extern crate patricia_trie;
2725
extern crate substrate_state_machine as state_machine;
2826
extern crate substrate_primitives as primitives;
2927
extern crate substrate_runtime_support as runtime_support;
@@ -38,23 +36,23 @@ extern crate kvdb_memorydb;
3836

3937
use std::sync::Arc;
4038
use std::path::PathBuf;
41-
use std::collections::HashMap;
4239

4340
use codec::Slicable;
44-
use ethereum_types::H256 as TrieH256;
45-
use hashdb::{DBValue, HashDB};
41+
use hashdb::DBValue;
4642
use kvdb_rocksdb::{Database, DatabaseConfig};
4743
use kvdb::{KeyValueDB, DBTransaction};
4844
use memorydb::MemoryDB;
4945
use parking_lot::RwLock;
50-
use patricia_trie::{TrieDB, TrieDBMut, TrieError, Trie, TrieMut};
5146
use runtime_primitives::generic::BlockId;
5247
use runtime_primitives::bft::Justification;
5348
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, Hashing, HashingFor, Zero};
5449
use runtime_primitives::BuildStorage;
5550
use state_machine::backend::Backend as StateBackend;
5651
use state_machine::CodeExecutor;
5752

53+
/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
54+
pub type DbState = state_machine::TrieBackend;
55+
5856
/// Database settings.
5957
pub struct DatabaseSettings {
6058
/// Cache size in bytes. If `None` default is used.
@@ -301,133 +299,6 @@ impl<Block: BlockT> client::backend::BlockImportOperation<Block> for BlockImport
301299
}
302300
}
303301

304-
struct Ephemeral<'a> {
305-
backing: &'a KeyValueDB,
306-
overlay: &'a mut MemoryDB,
307-
}
308-
309-
impl<'a> HashDB for Ephemeral<'a> {
310-
fn keys(&self) -> HashMap<TrieH256, i32> {
311-
self.overlay.keys() // TODO: iterate backing
312-
}
313-
314-
fn get(&self, key: &TrieH256) -> Option<DBValue> {
315-
match self.overlay.raw(key) {
316-
Some((val, i)) => {
317-
if i <= 0 {
318-
None
319-
} else {
320-
Some(val)
321-
}
322-
}
323-
None => {
324-
match self.backing.get(::columns::STATE, &key.0[..]) {
325-
Ok(x) => x,
326-
Err(e) => {
327-
warn!("Failed to read from DB: {}", e);
328-
None
329-
}
330-
}
331-
}
332-
}
333-
}
334-
335-
fn contains(&self, key: &TrieH256) -> bool {
336-
self.get(key).is_some()
337-
}
338-
339-
fn insert(&mut self, value: &[u8]) -> TrieH256 {
340-
self.overlay.insert(value)
341-
}
342-
343-
fn emplace(&mut self, key: TrieH256, value: DBValue) {
344-
self.overlay.emplace(key, value)
345-
}
346-
347-
fn remove(&mut self, key: &TrieH256) {
348-
self.overlay.remove(key)
349-
}
350-
}
351-
352-
/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
353-
#[derive(Clone)]
354-
pub struct DbState {
355-
db: Arc<KeyValueDB>,
356-
root: TrieH256,
357-
}
358-
359-
impl state_machine::Backend for DbState {
360-
type Error = client::error::Error;
361-
type Transaction = MemoryDB;
362-
363-
fn storage(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
364-
let mut read_overlay = MemoryDB::default();
365-
let eph = Ephemeral {
366-
backing: &*self.db,
367-
overlay: &mut read_overlay,
368-
};
369-
370-
let map_e = |e: Box<TrieError>| ::client::error::Error::from(format!("Trie lookup error: {}", e));
371-
372-
TrieDB::new(&eph, &self.root).map_err(map_e)?
373-
.get(key).map(|x| x.map(|val| val.to_vec())).map_err(map_e)
374-
}
375-
376-
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
377-
let mut read_overlay = MemoryDB::default();
378-
let eph = Ephemeral {
379-
backing: &*self.db,
380-
overlay: &mut read_overlay,
381-
};
382-
383-
let collect_all = || -> Result<_, Box<TrieError>> {
384-
let trie = TrieDB::new(&eph, &self.root)?;
385-
let mut v = Vec::new();
386-
for x in trie.iter()? {
387-
let (key, value) = x?;
388-
v.push((key.to_vec(), value.to_vec()));
389-
}
390-
391-
Ok(v)
392-
};
393-
394-
match collect_all() {
395-
Ok(v) => v,
396-
Err(e) => {
397-
debug!("Error extracting trie values: {}", e);
398-
Vec::new()
399-
}
400-
}
401-
}
402-
403-
fn storage_root<I>(&self, delta: I) -> ([u8; 32], MemoryDB)
404-
where I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)>
405-
{
406-
let mut write_overlay = MemoryDB::default();
407-
let mut root = self.root;
408-
{
409-
let mut eph = Ephemeral {
410-
backing: &*self.db,
411-
overlay: &mut write_overlay,
412-
};
413-
414-
let mut trie = TrieDBMut::from_existing(&mut eph, &mut root).expect("prior state root to exist"); // TODO: handle gracefully
415-
for (key, change) in delta {
416-
let result = match change {
417-
Some(val) => trie.insert(&key, &val),
418-
None => trie.remove(&key), // TODO: archive mode
419-
};
420-
421-
if let Err(e) = result {
422-
warn!("Failed to write to trie: {}", e);
423-
}
424-
}
425-
}
426-
427-
(root.0.into(), write_overlay)
428-
}
429-
}
430-
431302
/// Disk backend. Keeps data in a key-value store. In archive mode, trie nodes are kept from all blocks.
432303
/// Otherwise, trie nodes are kept only from the most recent block.
433304
pub struct Backend<Block: BlockT> {
@@ -523,25 +394,14 @@ impl<Block: BlockT> client::backend::Backend<Block> for Backend<Block> where
523394

524395
// special case for genesis initialization
525396
match block {
526-
BlockId::Hash(h) if h == Default::default() => {
527-
let mut root = TrieH256::default();
528-
let mut db = MemoryDB::default();
529-
TrieDBMut::new(&mut db, &mut root);
530-
531-
return Ok(DbState {
532-
db: self.db.clone(),
533-
root,
534-
})
535-
}
397+
BlockId::Hash(h) if h == Default::default() =>
398+
return Ok(DbState::with_kvdb_for_genesis(self.db.clone(), ::columns::STATE)),
536399
_ => {}
537400
}
538401

539402
self.blockchain.header(block).and_then(|maybe_hdr| maybe_hdr.map(|hdr| {
540403
let root: [u8; 32] = hdr.state_root().clone().into();
541-
DbState {
542-
db: self.db.clone(),
543-
root: root.into(),
544-
}
404+
DbState::with_kvdb(self.db.clone(), ::columns::STATE, root.into())
545405
}).ok_or_else(|| client::error::ErrorKind::UnknownBlock(format!("{:?}", block)).into()))
546406
}
547407
}
@@ -553,6 +413,7 @@ impl<Block: BlockT> client::backend::LocalBackend<Block> for Backend<Block> wher
553413

554414
#[cfg(test)]
555415
mod tests {
416+
use hashdb::HashDB;
556417
use super::*;
557418
use client::backend::Backend as BTrait;
558419
use client::backend::BlockImportOperation as Op;

substrate/client/src/call_executor.rs

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
use std::sync::Arc;
1818
use futures::{IntoFuture, Future};
1919
use runtime_primitives::generic::BlockId;
20-
use runtime_primitives::traits::Block as BlockT;
20+
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
2121
use state_machine::{self, OverlayedChanges, Backend as StateBackend, CodeExecutor};
22-
use state_machine::backend::InMemory as InMemoryStateBackend;
2322

2423
use backend;
2524
use blockchain::Backend as ChainBackend;
@@ -49,6 +48,11 @@ pub trait CallExecutor<B: BlockT> {
4948
///
5049
/// No changes are made.
5150
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>;
51+
52+
/// Execute a call to a contract on top of given state, gathering execution proof.
53+
///
54+
/// No changes are made.
55+
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>;
5256
}
5357

5458
/// Call executor that executes methods locally, querying all required
@@ -105,6 +109,18 @@ impl<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E>
105109
call_data,
106110
).map_err(Into::into)
107111
}
112+
113+
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> {
114+
state_machine::prove(
115+
state,
116+
changes,
117+
&self.executor,
118+
method,
119+
call_data,
120+
)
121+
.map(|(result, proof, _)| (result, proof))
122+
.map_err(Into::into)
123+
}
108124
}
109125

110126
impl<B, F> RemoteCallExecutor<B, F> {
@@ -140,69 +156,70 @@ impl<B, F, Block> CallExecutor<Block> for RemoteCallExecutor<B, F>
140156
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)> {
141157
Err(error::ErrorKind::NotAvailableOnLightClient.into())
142158
}
159+
160+
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> {
161+
Err(error::ErrorKind::NotAvailableOnLightClient.into())
162+
}
143163
}
144164

145-
/// Check remote execution proof.
146-
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>
165+
/// Check remote execution proof using given backend.
166+
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>
147167
where
148168
B: backend::RemoteBackend<Block>,
149169
E: CodeExecutor,
150170
Block: BlockT,
171+
<<Block as BlockT>::Header as HeaderT>::Hash: Into<[u8; 32]>, // TODO: remove when patricia_trie generic.
151172
error::Error: From<<<B as backend::Backend<Block>>::State as StateBackend>::Error>,
152173
{
153-
use runtime_primitives::traits::{Header, Hashing, HashingFor};
154-
155-
let (remote_result, remote_proof) = remote_proof;
156-
157-
let remote_state = state_from_execution_proof(remote_proof);
158-
let remote_state_root = HashingFor::<Block>::trie_root(remote_state.pairs().into_iter());
159-
160174
let local_header = backend.blockchain().header(BlockId::Hash(request.block))?;
161-
let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", request.block)))?;
175+
let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{}", request.block)))?;
162176
let local_state_root = local_header.state_root().clone();
177+
do_check_execution_proof(local_state_root, executor, request, remote_proof)
178+
}
163179

164-
if remote_state_root != local_state_root {
165-
return Err(error::ErrorKind::InvalidExecutionProof.into());
166-
}
167-
180+
/// Check remote execution proof using given state root.
181+
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>
182+
where
183+
E: CodeExecutor,
184+
H: Into<[u8; 32]>, // TODO: remove when patricia_trie generic.
185+
{
168186
let mut changes = OverlayedChanges::default();
169-
let (local_result, _) = state_machine::execute(
170-
&remote_state,
187+
let (local_result, _) = state_machine::proof_check(
188+
local_state_root.into(),
189+
remote_proof,
171190
&mut changes,
172191
executor,
173192
&request.method,
174-
&request.call_data,
175-
)?;
176-
177-
if local_result != remote_result {
178-
return Err(error::ErrorKind::InvalidExecutionProof.into());
179-
}
193+
&request.call_data)?;
180194

181195
Ok(CallResult { return_data: local_result, changes })
182196
}
183197

184-
/// Convert state to execution proof. Proof is simple the whole state (temporary).
185-
// TODO [light]: this method must be removed after trie-based proofs are landed.
186-
pub fn state_to_execution_proof<B: state_machine::Backend>(state: &B) -> Vec<Vec<u8>> {
187-
state.pairs().into_iter()
188-
.flat_map(|(k, v)| ::std::iter::once(k).chain(::std::iter::once(v)))
189-
.collect()
190-
}
191-
192-
/// Convert execution proof to in-memory state for check. Reverse function for state_to_execution_proof.
193-
// TODO [light]: this method must be removed after trie-based proofs are landed.
194-
fn state_from_execution_proof(proof: Vec<Vec<u8>>) -> InMemoryStateBackend {
195-
let mut changes = Vec::new();
196-
let mut proof_iter = proof.into_iter();
197-
loop {
198-
let key = proof_iter.next();
199-
let value = proof_iter.next();
200-
if let (Some(key), Some(value)) = (key, value) {
201-
changes.push((key, Some(value)));
202-
} else {
203-
break;
204-
}
198+
#[cfg(test)]
199+
mod tests {
200+
use runtime_primitives::generic::BlockId;
201+
use state_machine::Backend;
202+
use test_client;
203+
use light::RemoteCallRequest;
204+
use super::do_check_execution_proof;
205+
206+
#[test]
207+
fn execution_proof_is_generated_and_checked() {
208+
// prepare remote client
209+
let remote_client = test_client::new();
210+
let remote_block_id = BlockId::Number(0);
211+
let remote_block_storage_root = remote_client.state_at(&remote_block_id)
212+
.unwrap().storage_root(::std::iter::empty()).0;
213+
214+
// 'fetch' execution proof from remote node
215+
let remote_execution_proof = remote_client.execution_proof(&remote_block_id, "authorities", &[]).unwrap().1;
216+
217+
// check remote execution proof locally
218+
let local_executor = test_client::NativeExecutor::new();
219+
do_check_execution_proof(remote_block_storage_root, &local_executor, &RemoteCallRequest {
220+
block: Default::default(),
221+
method: "authorities".into(),
222+
call_data: vec![],
223+
}, remote_execution_proof).unwrap();
205224
}
206-
207-
InMemoryStateBackend::default().update(changes)
208225
}

substrate/client/src/client.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,7 @@ impl<B, E, Block: BlockT> Client<B, E, Block> where
225225
///
226226
/// No changes are made.
227227
pub fn execution_proof(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> error::Result<(Vec<u8>, Vec<Vec<u8>>)> {
228-
use call_executor::state_to_execution_proof;
229-
230-
let result = self.executor.call(id, method, call_data);
231-
let result = result?.return_data;
232-
let proof = self.backend.state_at(*id).map(|state| state_to_execution_proof(&state))?;
233-
Ok((result, proof))
228+
self.state_at(id).and_then(|state| self.executor.prove_at_state(state, &mut Default::default(), method, call_data))
234229
}
235230

236231
/// Set up the native execution environment to call into a native runtime code.

0 commit comments

Comments
 (0)