mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 21:11:07 +00:00
Fetching changes proof from remote nodes (#769)
* changes_trie * changs_trie: continue * changes_trie: adding tests * fixed TODO * removed obsolete ExtrinsicChanges * encodable ChangesTrieConfiguration * removed polkadot fle * fixed grumbles * ext_storage_changes_root returns u32 * moved changes trie root to digest * removed commented code * read storage values from native code * fixed grumbles * fixed grumbles * missing comma * key changes proof generation + query * fix grumbles * check that changes trie config is not changed by block.finalize() * fixed changes trie config check
This commit is contained in:
committed by
Gav Wood
parent
fdfd4672c1
commit
c54350661d
@@ -363,7 +363,7 @@ pub struct DbChangesTrieStorage<Block: BlockT> {
|
||||
_phantom: ::std::marker::PhantomData<Block>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> state_machine::ChangesTrieStorage<Blake2Hasher> for DbChangesTrieStorage<Block> {
|
||||
impl<Block: BlockT> state_machine::ChangesTrieRootsStorage<Blake2Hasher> for DbChangesTrieStorage<Block> {
|
||||
fn root(&self, block: u64) -> Result<Option<H256>, String> {
|
||||
Ok(read_db::<Block>(&*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Number(As::sa(block)))
|
||||
.map_err(|err| format!("{}", err))
|
||||
@@ -378,7 +378,9 @@ impl<Block: BlockT> state_machine::ChangesTrieStorage<Blake2Hasher> for DbChange
|
||||
.and_then(DigestItem::as_changes_trie_root)
|
||||
.map(|root| H256::from_slice(root.as_ref()))))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> state_machine::ChangesTrieStorage<Blake2Hasher> for DbChangesTrieStorage<Block> {
|
||||
fn get(&self, key: &H256) -> Result<Option<DBValue>, String> {
|
||||
self.db.get(columns::CHANGES_TRIE, &key[..])
|
||||
.map_err(|err| format!("{}", err))
|
||||
@@ -750,7 +752,7 @@ mod tests {
|
||||
use client::backend::BlockImportOperation as Op;
|
||||
use client::blockchain::HeaderBackend as BlockchainHeaderBackend;
|
||||
use runtime_primitives::testing::{Header, Block as RawBlock};
|
||||
use state_machine::{TrieMut, TrieDBMut, ChangesTrieStorage};
|
||||
use state_machine::{TrieMut, TrieDBMut, ChangesTrieRootsStorage, ChangesTrieStorage};
|
||||
use test_client;
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
@@ -23,13 +23,14 @@ use primitives::AuthorityId;
|
||||
use runtime_primitives::{bft::Justification, generic::{BlockId, SignedBlock, Block as RuntimeBlock}};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, BlockNumberToHash};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use primitives::{Blake2Hasher, H256};
|
||||
use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration};
|
||||
use primitives::storage::{StorageKey, StorageData};
|
||||
use primitives::storage::well_known_keys;
|
||||
use codec::{Encode, Decode};
|
||||
use state_machine::{
|
||||
Backend as StateBackend, CodeExecutor,
|
||||
ExecutionStrategy, ExecutionManager, prove_read
|
||||
ExecutionStrategy, ExecutionManager, prove_read,
|
||||
key_changes, key_changes_proof,
|
||||
};
|
||||
|
||||
use backend::{self, BlockImportOperation};
|
||||
@@ -56,6 +57,7 @@ pub struct Client<B, E, Block> where Block: BlockT {
|
||||
importing_block: RwLock<Option<Block::Hash>>, // holds the block hash currently being imported. TODO: replace this with block queue
|
||||
block_execution_strategy: ExecutionStrategy,
|
||||
api_execution_strategy: ExecutionStrategy,
|
||||
changes_trie_config: Option<ChangesTrieConfiguration>,
|
||||
}
|
||||
|
||||
/// A source of blockchain events.
|
||||
@@ -239,6 +241,13 @@ impl<B, E, Block> Client<B, E, Block> where
|
||||
)?;
|
||||
backend.commit_operation(op)?;
|
||||
}
|
||||
|
||||
// changes trie configuration should never change => we can read it in advance
|
||||
let changes_trie_config = backend.state_at(BlockId::Number(Zero::zero()))?
|
||||
.storage(well_known_keys::CHANGES_TRIE_CONFIG)
|
||||
.map_err(|e| error::Error::from_state(Box::new(e)))?
|
||||
.and_then(|c| Decode::decode(&mut &*c));
|
||||
|
||||
Ok(Client {
|
||||
backend,
|
||||
executor,
|
||||
@@ -249,6 +258,7 @@ impl<B, E, Block> Client<B, E, Block> where
|
||||
importing_block: Default::default(),
|
||||
block_execution_strategy,
|
||||
api_execution_strategy,
|
||||
changes_trie_config,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -335,6 +345,65 @@ impl<B, E, Block> Client<B, E, Block> where
|
||||
Ok((header, proof))
|
||||
}
|
||||
|
||||
/// Get pairs of (block, extrinsic) where key has been changed at given blocks range.
|
||||
/// Works only for runtimes that are supporting changes tries.
|
||||
pub fn key_changes(
|
||||
&self,
|
||||
first: Block::Hash,
|
||||
last: Block::Hash,
|
||||
key: &[u8]
|
||||
) -> error::Result<Vec<(NumberFor<Block>, u32)>> {
|
||||
let config = self.changes_trie_config.as_ref();
|
||||
let storage = self.backend.changes_trie_storage();
|
||||
let (config, storage) = match (config, storage) {
|
||||
(Some(config), Some(storage)) => (config, storage),
|
||||
_ => return Err(error::ErrorKind::ChangesTriesNotSupported.into()),
|
||||
};
|
||||
|
||||
key_changes::<_, Blake2Hasher>(
|
||||
config,
|
||||
storage,
|
||||
self.require_block_number_from_id(&BlockId::Hash(first))?.as_(),
|
||||
self.require_block_number_from_id(&BlockId::Hash(last))?.as_(),
|
||||
self.backend.blockchain().info()?.best_number.as_(),
|
||||
key)
|
||||
.map_err(|err| error::ErrorKind::ChangesTrieAccessFailed(err).into())
|
||||
.map(|r| r.into_iter().map(|(b, e)| (As::sa(b), e)).collect())
|
||||
}
|
||||
|
||||
/// Get proof for computation of (block, extrinsic) pairs where key has been changed at given blocks range.
|
||||
/// `max` is the hash of the last block known to the requester - we can't use changes tries from descendants
|
||||
/// of this block.
|
||||
/// Works only for runtimes that are supporting changes tries.
|
||||
pub fn key_changes_proof(
|
||||
&self,
|
||||
first: Block::Hash,
|
||||
last: Block::Hash,
|
||||
max: Block::Hash,
|
||||
key: &[u8]
|
||||
) -> error::Result<(NumberFor<Block>, Vec<Vec<u8>>)> {
|
||||
let config = self.changes_trie_config.as_ref();
|
||||
let storage = self.backend.changes_trie_storage();
|
||||
let (config, storage) = match (config, storage) {
|
||||
(Some(config), Some(storage)) => (config, storage),
|
||||
_ => return Err(error::ErrorKind::ChangesTriesNotSupported.into()),
|
||||
};
|
||||
|
||||
let max_number = ::std::cmp::min(
|
||||
self.backend.blockchain().info()?.best_number,
|
||||
self.require_block_number_from_id(&BlockId::Hash(max))?,
|
||||
);
|
||||
key_changes_proof::<_, Blake2Hasher>(
|
||||
config,
|
||||
storage,
|
||||
self.require_block_number_from_id(&BlockId::Hash(first))?.as_(),
|
||||
self.require_block_number_from_id(&BlockId::Hash(last))?.as_(),
|
||||
max_number.as_(),
|
||||
key)
|
||||
.map_err(|err| error::ErrorKind::ChangesTrieAccessFailed(err).into())
|
||||
.map(|proof| (max_number, proof))
|
||||
}
|
||||
|
||||
/// Create a new block, built on the head of the chain.
|
||||
pub fn new_block(&self) -> error::Result<block_builder::BlockBuilder<B, E, Block, Blake2Hasher>>
|
||||
where E: Clone
|
||||
@@ -710,13 +779,19 @@ impl<B, E, Block> Client<B, E, Block> where
|
||||
}
|
||||
|
||||
/// Convert an arbitrary block ID into a block hash.
|
||||
pub fn block_number_from_id(&self, id: &BlockId<Block>) -> error::Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
|
||||
pub fn block_number_from_id(&self, id: &BlockId<Block>) -> error::Result<Option<NumberFor<Block>>> {
|
||||
match *id {
|
||||
BlockId::Hash(_) => Ok(self.header(id)?.map(|h| h.number().clone())),
|
||||
BlockId::Number(n) => Ok(Some(n)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.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)
|
||||
@@ -969,14 +1044,93 @@ impl<B, E, Block> BlockBody<Block> for Client<B, E, Block>
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub(crate) mod tests {
|
||||
use std::collections::HashMap;
|
||||
use super::*;
|
||||
use keyring::Keyring;
|
||||
use primitives::twox_128;
|
||||
use runtime_primitives::traits::{Digest as DigestT, DigestItem as DigestItemT};
|
||||
use runtime_primitives::generic::DigestItem;
|
||||
use test_client::{self, TestClient};
|
||||
use test_client::client::BlockOrigin;
|
||||
use test_client::client::backend::Backend as TestBackend;
|
||||
use test_client::BlockBuilderExt;
|
||||
use test_client::runtime::Transfer;
|
||||
use test_client::runtime::{self, Block, Transfer};
|
||||
|
||||
/// Returns tuple, consisting of:
|
||||
/// 1) test client pre-filled with blocks changing balances;
|
||||
/// 2) roots of changes tries for these blocks
|
||||
/// 3) test cases in form (begin, end, key, vec![(block, extrinsic)]) that are required to pass
|
||||
pub fn prepare_client_with_key_changes() -> (
|
||||
test_client::client::Client<test_client::Backend, test_client::Executor, Block>,
|
||||
Vec<H256>,
|
||||
Vec<(u64, u64, Vec<u8>, Vec<(u64, u32)>)>,
|
||||
) {
|
||||
// prepare block structure
|
||||
let blocks_transfers = vec![
|
||||
vec![(Keyring::Alice, Keyring::Dave), (Keyring::Bob, Keyring::Dave)],
|
||||
vec![(Keyring::Charlie, Keyring::Eve)],
|
||||
vec![],
|
||||
vec![(Keyring::Alice, Keyring::Dave)],
|
||||
];
|
||||
|
||||
// prepare client ang import blocks
|
||||
let mut local_roots = Vec::new();
|
||||
let remote_client = test_client::new_with_changes_trie();
|
||||
let mut nonces: HashMap<_, u64> = Default::default();
|
||||
for (i, block_transfers) in blocks_transfers.into_iter().enumerate() {
|
||||
let mut builder = remote_client.new_block().unwrap();
|
||||
for (from, to) in block_transfers {
|
||||
builder.push_transfer(Transfer {
|
||||
from: from.to_raw_public().into(),
|
||||
to: to.to_raw_public().into(),
|
||||
amount: 1,
|
||||
nonce: *nonces.entry(from).and_modify(|n| { *n = *n + 1 }).or_default(),
|
||||
}).unwrap();
|
||||
}
|
||||
remote_client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
|
||||
|
||||
let header = remote_client.header(&BlockId::Number(i as u64 + 1)).unwrap().unwrap();
|
||||
let trie_root = header.digest().logs().iter()
|
||||
.find(|l| l.as_changes_trie_root().is_some())
|
||||
.and_then(DigestItem::as_changes_trie_root)
|
||||
.map(|root| H256::from_slice(root.as_ref()))
|
||||
.unwrap();
|
||||
local_roots.push(trie_root);
|
||||
}
|
||||
|
||||
// prepare test cases
|
||||
let alice = twox_128(&runtime::system::balance_of_key(Keyring::Alice.to_raw_public().into())).to_vec();
|
||||
let bob = twox_128(&runtime::system::balance_of_key(Keyring::Bob.to_raw_public().into())).to_vec();
|
||||
let charlie = twox_128(&runtime::system::balance_of_key(Keyring::Charlie.to_raw_public().into())).to_vec();
|
||||
let dave = twox_128(&runtime::system::balance_of_key(Keyring::Dave.to_raw_public().into())).to_vec();
|
||||
let eve = twox_128(&runtime::system::balance_of_key(Keyring::Eve.to_raw_public().into())).to_vec();
|
||||
let ferdie = twox_128(&runtime::system::balance_of_key(Keyring::Ferdie.to_raw_public().into())).to_vec();
|
||||
let test_cases = vec![
|
||||
(1, 4, alice.clone(), vec![(4, 0), (1, 0)]),
|
||||
(1, 3, alice.clone(), vec![(1, 0)]),
|
||||
(2, 4, alice.clone(), vec![(4, 0)]),
|
||||
(2, 3, alice.clone(), vec![]),
|
||||
|
||||
(1, 4, bob.clone(), vec![(1, 1)]),
|
||||
(1, 1, bob.clone(), vec![(1, 1)]),
|
||||
(2, 4, bob.clone(), vec![]),
|
||||
|
||||
(1, 4, charlie.clone(), vec![(2, 0)]),
|
||||
|
||||
(1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]),
|
||||
(1, 1, dave.clone(), vec![(1, 1), (1, 0)]),
|
||||
(3, 4, dave.clone(), vec![(4, 0)]),
|
||||
|
||||
(1, 4, eve.clone(), vec![(2, 0)]),
|
||||
(1, 1, eve.clone(), vec![]),
|
||||
(3, 4, eve.clone(), vec![]),
|
||||
|
||||
(1, 4, ferdie.clone(), vec![]),
|
||||
];
|
||||
|
||||
(remote_client, local_roots, test_cases)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_initialises_from_genesis_ok() {
|
||||
@@ -1328,4 +1482,20 @@ mod tests {
|
||||
|
||||
assert_eq!(None, client.best_containing(d2.hash().clone(), Some(0)).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_changes_works() {
|
||||
let (client, _, test_cases) = prepare_client_with_key_changes();
|
||||
|
||||
for (index, (begin, end, key, expected_result)) in test_cases.into_iter().enumerate() {
|
||||
let begin = client.block_hash(begin).unwrap().unwrap();
|
||||
let end = client.block_hash(end).unwrap().unwrap();
|
||||
let actual_result = client.key_changes(begin, end, &key).unwrap();
|
||||
match actual_result == expected_result {
|
||||
true => (),
|
||||
false => panic!(format!("Failed test {}: actual = {:?}, expected = {:?}",
|
||||
index, actual_result, expected_result)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,18 @@ error_chain! {
|
||||
display("Error decoding call result of {}", method)
|
||||
}
|
||||
|
||||
/// Changes tries are not supported.
|
||||
ChangesTriesNotSupported {
|
||||
description("changes tries are not supported"),
|
||||
display("Changes tries are not supported by the runtime"),
|
||||
}
|
||||
|
||||
/// Key changes query has failed.
|
||||
ChangesTrieAccessFailed(e: String) {
|
||||
description("invalid changes proof"),
|
||||
display("Failed to check changes proof: {}", e),
|
||||
}
|
||||
|
||||
/// Last finalized block not parent of current.
|
||||
NonSequentialFinalization(s: String) {
|
||||
description("Did not finalize blocks in sequential order."),
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
//! Light client data fetcher. Fetches requested data from remote full nodes.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use futures::IntoFuture;
|
||||
|
||||
use hash_db::Hasher;
|
||||
use heapsize::HeapSizeOf;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use state_machine::{CodeExecutor, read_proof_check};
|
||||
use std::marker::PhantomData;
|
||||
use primitives::ChangesTrieConfiguration;
|
||||
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use state_machine::{CodeExecutor, ChangesTrieRootsStorage, read_proof_check,
|
||||
key_changes_proof_check};
|
||||
|
||||
use call_executor::CallResult;
|
||||
use cht;
|
||||
@@ -34,7 +36,7 @@ use light::call_executor::check_execution_proof;
|
||||
pub struct RemoteCallRequest<Header: HeaderT> {
|
||||
/// Call at state of given block.
|
||||
pub block: Header::Hash,
|
||||
/// Head of block at which call is perormed.
|
||||
/// Header of block at which call is performed.
|
||||
pub header: Header,
|
||||
/// Method to call.
|
||||
pub method: String,
|
||||
@@ -60,7 +62,7 @@ pub struct RemoteHeaderRequest<Header: HeaderT> {
|
||||
pub struct RemoteReadRequest<Header: HeaderT> {
|
||||
/// Read at state of given block.
|
||||
pub block: Header::Hash,
|
||||
/// Head of block at which read is perormed.
|
||||
/// Header of block at which read is performed.
|
||||
pub header: Header,
|
||||
/// Storage key to read.
|
||||
pub key: Vec<u8>,
|
||||
@@ -68,6 +70,28 @@ pub struct RemoteReadRequest<Header: HeaderT> {
|
||||
pub retry_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// Remote key changes read request.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RemoteChangesRequest<Header: HeaderT> {
|
||||
/// Changes trie configuration.
|
||||
pub changes_trie_config: ChangesTrieConfiguration,
|
||||
/// Query changes from range of blocks, starting (and including) with this hash...
|
||||
pub first_block: (Header::Number, Header::Hash),
|
||||
/// ...ending (and including) with this hash. Should come after first_block and
|
||||
/// be the part of the same fork.
|
||||
pub last_block: (Header::Number, Header::Hash),
|
||||
/// Only use digests from blocks up to this hash. Should be last_block OR come
|
||||
/// after this block and be the part of the same fork.
|
||||
pub max_block: (Header::Number, Header::Hash),
|
||||
// TODO: get rid of this + preserve change_trie_roots when replacing headers with CHT!!!
|
||||
/// Changes trie roots for the range of blocks [first_block..max_block].
|
||||
pub tries_roots: Vec<Header::Hash>,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
/// Light client data fetcher. Implementations of this trait must check if remote data
|
||||
/// is correct (see FetchedDataChecker) and return already checked data.
|
||||
pub trait Fetcher<Block: BlockT>: Send + Sync {
|
||||
@@ -77,6 +101,8 @@ pub trait Fetcher<Block: BlockT>: Send + Sync {
|
||||
type RemoteReadResult: IntoFuture<Item=Option<Vec<u8>>, Error=ClientError>;
|
||||
/// Remote call result future.
|
||||
type RemoteCallResult: IntoFuture<Item=CallResult, Error=ClientError>;
|
||||
/// Remote changes result future.
|
||||
type RemoteChangesResult: IntoFuture<Item=Vec<(NumberFor<Block>, u32)>, Error=ClientError>;
|
||||
|
||||
/// Fetch remote header.
|
||||
fn remote_header(&self, request: RemoteHeaderRequest<Block::Header>) -> Self::RemoteHeaderResult;
|
||||
@@ -84,6 +110,9 @@ pub trait Fetcher<Block: BlockT>: Send + Sync {
|
||||
fn remote_read(&self, request: RemoteReadRequest<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
|
||||
/// at a given blocks range.
|
||||
fn remote_changes(&self, request: RemoteChangesRequest<Block::Header>) -> Self::RemoteChangesResult;
|
||||
}
|
||||
|
||||
/// Light client remote data checker.
|
||||
@@ -110,6 +139,13 @@ pub trait FetchChecker<Block: BlockT>: Send + Sync {
|
||||
request: &RemoteCallRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<CallResult>;
|
||||
/// Check remote changes query proof.
|
||||
fn check_changes_proof(
|
||||
&self,
|
||||
request: &RemoteChangesRequest<Block::Header>,
|
||||
remote_max: NumberFor<Block>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Vec<(NumberFor<Block>, u32)>>;
|
||||
}
|
||||
|
||||
/// Remote data checker.
|
||||
@@ -168,6 +204,60 @@ impl<E, Block, H> FetchChecker<Block> for LightDataChecker<E, H>
|
||||
) -> ClientResult<CallResult> {
|
||||
check_execution_proof::<_, _, H>(&self.executor, request, remote_proof)
|
||||
}
|
||||
|
||||
fn check_changes_proof(
|
||||
&self,
|
||||
request: &RemoteChangesRequest<Block::Header>,
|
||||
remote_max: NumberFor<Block>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Vec<(NumberFor<Block>, u32)>> {
|
||||
// since we need roots of all changes tries for the range begin..max
|
||||
// => remote node can't use max block greater that one that we have passed
|
||||
if remote_max > request.max_block.0 || remote_max < request.last_block.0 {
|
||||
return Err(ClientErrorKind::ChangesTrieAccessFailed(format!(
|
||||
"Invalid max_block used by the remote node: {}. Local: {}..{}..{}",
|
||||
remote_max, request.first_block.0, request.last_block.0, request.max_block.0,
|
||||
)).into());
|
||||
}
|
||||
|
||||
let first_number = request.first_block.0.as_();
|
||||
key_changes_proof_check::<_, H>(
|
||||
&request.changes_trie_config,
|
||||
&RootsStorage {
|
||||
first: first_number,
|
||||
roots: &request.tries_roots,
|
||||
},
|
||||
remote_proof,
|
||||
first_number,
|
||||
request.last_block.0.as_(),
|
||||
remote_max.as_(),
|
||||
&request.key)
|
||||
.map(|pairs| pairs.into_iter().map(|(b, x)| (As::sa(b), x)).collect())
|
||||
.map_err(|err| ClientErrorKind::ChangesTrieAccessFailed(err).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A view of HashMap<Number, Hash> as a changes trie roots storage.
|
||||
struct RootsStorage<'a, Hash: 'a> {
|
||||
first: u64,
|
||||
roots: &'a [Hash],
|
||||
}
|
||||
|
||||
impl<'a, H, Hash> ChangesTrieRootsStorage<H> for RootsStorage<'a, Hash>
|
||||
where
|
||||
H: Hasher,
|
||||
Hash: 'a + Send + Sync + Clone + AsRef<[u8]>,
|
||||
{
|
||||
fn root(&self, block: u64) -> Result<Option<H::Out>, String> {
|
||||
Ok(block.checked_sub(self.first)
|
||||
.and_then(|index| self.roots.get(index as usize))
|
||||
.cloned()
|
||||
.map(|root| {
|
||||
let mut hasher_root: H::Out = Default::default();
|
||||
hasher_root.as_mut().copy_from_slice(root.as_ref());
|
||||
hasher_root
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -175,9 +265,11 @@ pub mod tests {
|
||||
use futures::future::{ok, err, FutureResult};
|
||||
use parking_lot::Mutex;
|
||||
use call_executor::CallResult;
|
||||
use client::tests::prepare_client_with_key_changes;
|
||||
use executor::{self, NativeExecutionDispatch};
|
||||
use error::Error as ClientError;
|
||||
use test_client::{self, TestClient, runtime::{Hash, Block, Header}};
|
||||
use test_client::{self, TestClient};
|
||||
use test_client::runtime::{self, Hash, Block, Header};
|
||||
use test_client::client::BlockOrigin;
|
||||
use in_mem::{Blockchain as InMemoryBlockchain};
|
||||
use light::fetcher::{Fetcher, FetchChecker, LightDataChecker,
|
||||
@@ -194,6 +286,7 @@ pub mod tests {
|
||||
type RemoteHeaderResult = FutureResult<Header, ClientError>;
|
||||
type RemoteReadResult = FutureResult<Option<Vec<u8>>, ClientError>;
|
||||
type RemoteCallResult = FutureResult<CallResult, ClientError>;
|
||||
type RemoteChangesResult = FutureResult<Vec<(NumberFor<Block>, u32)>, ClientError>;
|
||||
|
||||
fn remote_header(&self, _request: RemoteHeaderRequest<Header>) -> Self::RemoteHeaderResult {
|
||||
err("Not implemented on test node".into())
|
||||
@@ -206,6 +299,10 @@ pub mod tests {
|
||||
fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
|
||||
ok((*self.lock()).clone())
|
||||
}
|
||||
|
||||
fn remote_changes(&self, _request: RemoteChangesRequest<Header>) -> Self::RemoteChangesResult {
|
||||
err("Not implemented on test node".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_for_read_proof_check() -> (
|
||||
@@ -231,7 +328,7 @@ pub mod tests {
|
||||
None,
|
||||
None,
|
||||
::backend::NewBlockState::Final,
|
||||
);
|
||||
).unwrap();
|
||||
let local_executor = test_client::LocalExecutor::new();
|
||||
let local_checker = LightDataChecker::new(local_executor);
|
||||
(local_checker, remote_block_header, remote_read_proof, authorities_len)
|
||||
@@ -307,4 +404,80 @@ pub mod tests {
|
||||
retry_count: None,
|
||||
}, Some(remote_block_header.clone()), remote_header_proof).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changes_proof_is_generated_and_checked() {
|
||||
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
|
||||
let local_checker = LightDataChecker::<_, Blake2Hasher>::new(
|
||||
test_client::LocalExecutor::new());
|
||||
let local_checker = &local_checker as &FetchChecker<Block>;
|
||||
let max = remote_client.info().unwrap().chain.best_number;
|
||||
let max_hash = remote_client.info().unwrap().chain.best_hash;
|
||||
|
||||
for (index, (begin, end, key, expected_result)) in test_cases.into_iter().enumerate() {
|
||||
let begin_hash = remote_client.block_hash(begin).unwrap().unwrap();
|
||||
let end_hash = remote_client.block_hash(end).unwrap().unwrap();
|
||||
|
||||
// 'fetch' changes proof from remote node
|
||||
let (remote_max, remote_proof) = remote_client.key_changes_proof(
|
||||
begin_hash, end_hash, max_hash, &key
|
||||
).unwrap();
|
||||
|
||||
// check proof on local client
|
||||
let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec();
|
||||
let request = RemoteChangesRequest::<Header> {
|
||||
changes_trie_config: runtime::changes_trie_config(),
|
||||
first_block: (begin, begin_hash),
|
||||
last_block: (end, end_hash),
|
||||
max_block: (max, max_hash),
|
||||
tries_roots: local_roots_range,
|
||||
key: key,
|
||||
retry_count: None,
|
||||
};
|
||||
let local_result = local_checker.check_changes_proof(
|
||||
&request, remote_max, remote_proof).unwrap();
|
||||
|
||||
// ..and ensure that result is the same as on remote node
|
||||
match local_result == expected_result {
|
||||
true => (),
|
||||
false => panic!(format!("Failed test {}: local = {:?}, expected = {:?}",
|
||||
index, local_result, expected_result)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_changes_proof_fails_if_proof_is_wrong() {
|
||||
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
|
||||
let local_checker = LightDataChecker::<_, Blake2Hasher>::new(
|
||||
test_client::LocalExecutor::new());
|
||||
let local_checker = &local_checker as &FetchChecker<Block>;
|
||||
let max = remote_client.info().unwrap().chain.best_number;
|
||||
let max_hash = remote_client.info().unwrap().chain.best_hash;
|
||||
|
||||
let (begin, end, key, _) = test_cases[0].clone();
|
||||
let begin_hash = remote_client.block_hash(begin).unwrap().unwrap();
|
||||
let end_hash = remote_client.block_hash(end).unwrap().unwrap();
|
||||
|
||||
// 'fetch' changes proof from remote node
|
||||
let (remote_max, mut remote_proof) = remote_client.key_changes_proof(
|
||||
begin_hash, end_hash, max_hash, &key).unwrap();
|
||||
let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec();
|
||||
let request = RemoteChangesRequest::<Header> {
|
||||
changes_trie_config: runtime::changes_trie_config(),
|
||||
first_block: (begin, begin_hash),
|
||||
last_block: (end, end_hash),
|
||||
max_block: (max, max_hash),
|
||||
tries_roots: local_roots_range.clone(),
|
||||
key: key,
|
||||
retry_count: None,
|
||||
};
|
||||
|
||||
// check proof on local client using max from the future
|
||||
assert!(local_checker.check_changes_proof(&request, remote_max + 1, remote_proof.clone()).is_err());
|
||||
|
||||
// check proof on local client using broken proof
|
||||
remote_proof = local_roots_range.into_iter().map(|v| v.to_vec()).collect();
|
||||
assert!(local_checker.check_changes_proof(&request, remote_max, remote_proof).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
use client::{self, Client as SubstrateClient, ImportResult, ClientInfo, BlockStatus, BlockOrigin, CallExecutor};
|
||||
use client::error::Error;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::bft::Justification;
|
||||
use primitives::{Blake2Hasher};
|
||||
@@ -61,6 +61,15 @@ pub trait Client<Block: BlockT>: Send + Sync {
|
||||
|
||||
/// Get method execution proof.
|
||||
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error>;
|
||||
|
||||
/// Get key changes proof.
|
||||
fn key_changes_proof(
|
||||
&self,
|
||||
first: Block::Hash,
|
||||
last: Block::Hash,
|
||||
max: Block::Hash,
|
||||
key: &[u8]
|
||||
) -> Result<(NumberFor<Block>, Vec<Vec<u8>>), Error>;
|
||||
}
|
||||
|
||||
impl<B, E, Block> Client<Block> for SubstrateClient<B, E, Block> where
|
||||
@@ -116,4 +125,14 @@ impl<B, E, Block> Client<Block> for SubstrateClient<B, E, Block> where
|
||||
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).execution_proof(&BlockId::Hash(block.clone()), method, data)
|
||||
}
|
||||
|
||||
fn key_changes_proof(
|
||||
&self,
|
||||
first: Block::Hash,
|
||||
last: Block::Hash,
|
||||
max: Block::Hash,
|
||||
key: &[u8]
|
||||
) -> Result<(NumberFor<Block>, Vec<Vec<u8>>), Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).key_changes_proof(first, last, max, key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use codec::{Encode, Decode, Input, Output};
|
||||
pub use self::generic::{
|
||||
BlockAnnounce, RemoteCallRequest, RemoteReadRequest,
|
||||
RemoteHeaderRequest, RemoteHeaderResponse, ConsensusVote,
|
||||
SignedConsensusVote, FromBlock
|
||||
RemoteHeaderRequest, RemoteHeaderResponse,
|
||||
RemoteChangesRequest, RemoteChangesResponse,
|
||||
ConsensusVote, SignedConsensusVote, FromBlock
|
||||
};
|
||||
|
||||
/// A unique ID of a request.
|
||||
@@ -274,6 +275,10 @@ pub mod generic {
|
||||
RemoteHeaderRequest(RemoteHeaderRequest<Number>),
|
||||
/// Remote header response.
|
||||
RemoteHeaderResponse(RemoteHeaderResponse<Header>),
|
||||
/// Remote changes request.
|
||||
RemoteChangesRequest(RemoteChangesRequest<Hash>),
|
||||
/// Remote changes reponse.
|
||||
RemoteChangesResponse(RemoteChangesResponse<Number>),
|
||||
/// Chain-specific message
|
||||
#[codec(index = "255")]
|
||||
ChainSpecific(Vec<u8>),
|
||||
@@ -372,4 +377,31 @@ pub mod generic {
|
||||
/// Header proof.
|
||||
pub proof: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote changes request.
|
||||
pub struct RemoteChangesRequest<H> {
|
||||
/// Unique request id.
|
||||
pub id: RequestId,
|
||||
/// Hash of the first block of the range (including first) where changes are requested.
|
||||
pub first: H,
|
||||
/// Hash of the last block of the range (including last) where changes are requested.
|
||||
pub last: H,
|
||||
/// Hash of the last block that we can use when querying changes.
|
||||
pub max: H,
|
||||
/// Storage key which changes are requested.
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote changes response.
|
||||
pub struct RemoteChangesResponse<N> {
|
||||
/// Id of a request this response was made for.
|
||||
pub id: RequestId,
|
||||
/// Proof has been generated using block with this number as a max block. Should be
|
||||
/// less than or equal to the RemoteChangesRequest::max block number.
|
||||
pub max: N,
|
||||
/// Changes proof.
|
||||
pub proof: Vec<Vec<u8>>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ use futures::sync::oneshot::{channel, Receiver, Sender};
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use linked_hash_map::Entry;
|
||||
use parking_lot::Mutex;
|
||||
use client;
|
||||
use client::{self, error::{Error as ClientError, ErrorKind as ClientErrorKind}};
|
||||
use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest,
|
||||
RemoteCallRequest, RemoteReadRequest};
|
||||
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest};
|
||||
use io::SyncIo;
|
||||
use message;
|
||||
use network_libp2p::{Severity, NodeIndex};
|
||||
use service;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
|
||||
|
||||
/// Remote request timeout.
|
||||
const REQUEST_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
@@ -62,6 +62,14 @@ pub trait OnDemandService<Block: BlockT>: Send + Sync {
|
||||
|
||||
/// When call response is received from remote node.
|
||||
fn on_remote_call_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteCallResponse);
|
||||
|
||||
/// When changes response is received from remote node.
|
||||
fn on_remote_changes_response(
|
||||
&self,
|
||||
io: &mut SyncIo,
|
||||
peer: NodeIndex,
|
||||
response: message::RemoteChangesResponse<NumberFor<Block>>
|
||||
);
|
||||
}
|
||||
|
||||
/// On-demand requests service. Dispatches requests to appropriate peers.
|
||||
@@ -72,7 +80,7 @@ pub struct OnDemand<B: BlockT, E: service::ExecuteInContext<B>> {
|
||||
|
||||
/// On-demand remote call response.
|
||||
pub struct RemoteResponse<T> {
|
||||
receiver: Receiver<Result<T, client::error::Error>>,
|
||||
receiver: Receiver<Result<T, ClientError>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -92,24 +100,25 @@ struct Request<Block: BlockT> {
|
||||
}
|
||||
|
||||
enum RequestData<Block: BlockT> {
|
||||
RemoteHeader(RemoteHeaderRequest<Block::Header>, Sender<Result<Block::Header, client::error::Error>>),
|
||||
RemoteRead(RemoteReadRequest<Block::Header>, Sender<Result<Option<Vec<u8>>, client::error::Error>>),
|
||||
RemoteCall(RemoteCallRequest<Block::Header>, Sender<Result<client::CallResult, client::error::Error>>),
|
||||
RemoteHeader(RemoteHeaderRequest<Block::Header>, Sender<Result<Block::Header, ClientError>>),
|
||||
RemoteRead(RemoteReadRequest<Block::Header>, Sender<Result<Option<Vec<u8>>, ClientError>>),
|
||||
RemoteCall(RemoteCallRequest<Block::Header>, Sender<Result<client::CallResult, ClientError>>),
|
||||
RemoteChanges(RemoteChangesRequest<Block::Header>, Sender<Result<Vec<(NumberFor<Block>, u32)>, ClientError>>),
|
||||
}
|
||||
|
||||
enum Accept<Block: BlockT> {
|
||||
Ok,
|
||||
CheckFailed(client::error::Error, RequestData<Block>),
|
||||
CheckFailed(ClientError, RequestData<Block>),
|
||||
Unexpected(RequestData<Block>),
|
||||
}
|
||||
|
||||
impl<T> Future for RemoteResponse<T> {
|
||||
type Item = T;
|
||||
type Error = client::error::Error;
|
||||
type Error = ClientError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.receiver.poll()
|
||||
.map_err(|_| client::error::ErrorKind::RemoteFetchCancelled.into())
|
||||
.map_err(|_| ClientErrorKind::RemoteFetchCancelled.into())
|
||||
.and_then(|r| match r {
|
||||
Async::Ready(Ok(ready)) => Ok(Async::Ready(ready)),
|
||||
Async::Ready(Err(error)) => Err(error),
|
||||
@@ -172,7 +181,7 @@ impl<B: BlockT, E> OnDemand<B, E> where
|
||||
(retry_count - 1, Some(retry_request_data))
|
||||
} else {
|
||||
trace!(target: "sync", "Failed to get remote {} response for given number of retries", rtype);
|
||||
retry_request_data.fail(client::error::ErrorKind::RemoteFetchFailed.into());
|
||||
retry_request_data.fail(ClientErrorKind::RemoteFetchFailed.into());
|
||||
(0, None)
|
||||
}
|
||||
},
|
||||
@@ -262,6 +271,22 @@ impl<B, E> OnDemandService<B> for OnDemand<B, E> where
|
||||
data @ _ => Accept::Unexpected(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_remote_changes_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteChangesResponse<NumberFor<B>>) {
|
||||
self.accept_response("changes", io, peer, response.id, |request| match request.data {
|
||||
RequestData::RemoteChanges(request, sender) => match self.checker.check_changes_proof(
|
||||
&request, response.max, 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::RemoteChanges(request, sender)),
|
||||
},
|
||||
data @ _ => Accept::Unexpected(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> Fetcher<B> for OnDemand<B, E> where
|
||||
@@ -272,6 +297,7 @@ impl<B, E> Fetcher<B> for OnDemand<B, E> where
|
||||
type RemoteHeaderResult = RemoteResponse<B::Header>;
|
||||
type RemoteReadResult = RemoteResponse<Option<Vec<u8>>>;
|
||||
type RemoteCallResult = RemoteResponse<client::CallResult>;
|
||||
type RemoteChangesResult = RemoteResponse<Vec<(NumberFor<B>, u32)>>;
|
||||
|
||||
fn remote_header(&self, request: RemoteHeaderRequest<B::Header>) -> Self::RemoteHeaderResult {
|
||||
let (sender, receiver) = channel();
|
||||
@@ -290,6 +316,12 @@ impl<B, E> Fetcher<B> for OnDemand<B, E> where
|
||||
self.schedule_request(request.retry_count.clone(), RequestData::RemoteCall(request, sender),
|
||||
RemoteResponse { receiver })
|
||||
}
|
||||
|
||||
fn remote_changes(&self, request: RemoteChangesRequest<B::Header>) -> Self::RemoteChangesResult {
|
||||
let (sender, receiver) = channel();
|
||||
self.schedule_request(request.retry_count.clone(), RequestData::RemoteChanges(request, sender),
|
||||
RemoteResponse { receiver })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> OnDemandCore<B, E> where
|
||||
@@ -377,35 +409,44 @@ impl<B, E> OnDemandCore<B, E> where
|
||||
impl<Block: BlockT> Request<Block> {
|
||||
pub fn message(&self) -> message::Message<Block> {
|
||||
match self.data {
|
||||
RequestData::RemoteHeader(ref data, _) => message::generic::Message::RemoteHeaderRequest(
|
||||
message::RemoteHeaderRequest {
|
||||
RequestData::RemoteHeader(ref data, _) =>
|
||||
message::generic::Message::RemoteHeaderRequest(message::RemoteHeaderRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
}),
|
||||
RequestData::RemoteRead(ref data, _) => message::generic::Message::RemoteReadRequest(
|
||||
message::RemoteReadRequest {
|
||||
RequestData::RemoteRead(ref data, _) =>
|
||||
message::generic::Message::RemoteReadRequest(message::RemoteReadRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
key: data.key.clone(),
|
||||
}),
|
||||
RequestData::RemoteCall(ref data, _) => message::generic::Message::RemoteCallRequest(
|
||||
message::RemoteCallRequest {
|
||||
RequestData::RemoteCall(ref data, _) =>
|
||||
message::generic::Message::RemoteCallRequest(message::RemoteCallRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
method: data.method.clone(),
|
||||
data: data.call_data.clone(),
|
||||
}),
|
||||
RequestData::RemoteChanges(ref data, _) =>
|
||||
message::generic::Message::RemoteChangesRequest(message::RemoteChangesRequest {
|
||||
id: self.id,
|
||||
first: data.first_block.1.clone(),
|
||||
last: data.last_block.1.clone(),
|
||||
max: data.max_block.1.clone(),
|
||||
key: data.key.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> RequestData<Block> {
|
||||
pub fn fail(self, error: client::error::Error) {
|
||||
pub fn fail(self, error: ClientError) {
|
||||
// don't care if anyone is listening
|
||||
match self {
|
||||
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::RemoteChanges(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,15 +458,15 @@ pub mod tests {
|
||||
use std::time::Instant;
|
||||
use futures::Future;
|
||||
use parking_lot::RwLock;
|
||||
use client;
|
||||
use client::{self, error::{ErrorKind as ClientErrorKind, Result as ClientResult}};
|
||||
use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest,
|
||||
RemoteCallRequest, RemoteReadRequest};
|
||||
RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest};
|
||||
use message;
|
||||
use network_libp2p::NodeIndex;
|
||||
use service::{Roles, ExecuteInContext};
|
||||
use test::TestIo;
|
||||
use super::{REQUEST_TIMEOUT, OnDemand, OnDemandService};
|
||||
use test_client::runtime::{Block, Header};
|
||||
use test_client::runtime::{changes_trie_config, Block, Header};
|
||||
|
||||
pub struct DummyExecutor;
|
||||
struct DummyFetchChecker { ok: bool }
|
||||
@@ -440,27 +481,34 @@ pub mod tests {
|
||||
_request: &RemoteHeaderRequest<Header>,
|
||||
header: Option<Header>,
|
||||
_remote_proof: Vec<Vec<u8>>
|
||||
) -> client::error::Result<Header> {
|
||||
) -> ClientResult<Header> {
|
||||
match self.ok {
|
||||
true if header.is_some() => Ok(header.unwrap()),
|
||||
_ => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
|
||||
_ => Err(ClientErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_read_proof(&self, _request: &RemoteReadRequest<Header>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<Option<Vec<u8>>> {
|
||||
fn check_read_proof(&self, _: &RemoteReadRequest<Header>, _: Vec<Vec<u8>>) -> ClientResult<Option<Vec<u8>>> {
|
||||
match self.ok {
|
||||
true => Ok(Some(vec![42])),
|
||||
false => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
|
||||
false => Err(ClientErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_execution_proof(&self, _request: &RemoteCallRequest<Header>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<client::CallResult> {
|
||||
fn check_execution_proof(&self, _: &RemoteCallRequest<Header>, _: Vec<Vec<u8>>) -> ClientResult<client::CallResult> {
|
||||
match self.ok {
|
||||
true => Ok(client::CallResult {
|
||||
return_data: vec![42],
|
||||
changes: Default::default(),
|
||||
}),
|
||||
false => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
|
||||
false => Err(ClientErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_changes_proof(&self, _: &RemoteChangesRequest<Header>, _: u64, _: Vec<Vec<u8>>) -> ClientResult<Vec<(u64, u32)>> {
|
||||
match self.ok {
|
||||
true => Ok(vec![(100, 2)]),
|
||||
false => Err(ClientErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,4 +781,33 @@ pub mod tests {
|
||||
});
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_changes_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
let response = on_demand.remote_changes(RemoteChangesRequest {
|
||||
changes_trie_config: changes_trie_config(),
|
||||
first_block: (1, Default::default()),
|
||||
last_block: (100, Default::default()),
|
||||
max_block: (100, Default::default()),
|
||||
tries_roots: vec![],
|
||||
key: vec![],
|
||||
retry_count: None,
|
||||
});
|
||||
let thread = ::std::thread::spawn(move || {
|
||||
let result = response.wait().unwrap();
|
||||
assert_eq!(result, vec![(100, 2)]);
|
||||
});
|
||||
|
||||
on_demand.on_remote_changes_response(&mut network, 0, message::RemoteChangesResponse {
|
||||
id: 0,
|
||||
max: 1000,
|
||||
proof: vec![vec![2]],
|
||||
});
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::sync::Arc;
|
||||
use std::time;
|
||||
use parking_lot::RwLock;
|
||||
use rustc_hex::ToHex;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor, NumberFor, As};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor, NumberFor, As, Zero};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use network_libp2p::{NodeIndex, Severity};
|
||||
use codec::{Encode, Decode};
|
||||
@@ -273,6 +273,8 @@ impl<B: BlockT, S: Specialization<B>, H: ExHashT> Protocol<B, S, H> {
|
||||
GenericMessage::RemoteReadResponse(response) => self.on_remote_read_response(io, who, response),
|
||||
GenericMessage::RemoteHeaderRequest(request) => self.on_remote_header_request(io, who, request),
|
||||
GenericMessage::RemoteHeaderResponse(response) => self.on_remote_header_response(io, who, response),
|
||||
GenericMessage::RemoteChangesRequest(request) => self.on_remote_changes_request(io, who, request),
|
||||
GenericMessage::RemoteChangesResponse(response) => self.on_remote_changes_response(io, who, response),
|
||||
other => self.specialization.write().on_message(&mut ProtocolContext::new(&self.context_data, io), who, other),
|
||||
}
|
||||
}
|
||||
@@ -648,6 +650,29 @@ impl<B: BlockT, S: Specialization<B>, H: ExHashT> Protocol<B, S, H> {
|
||||
self.on_demand.as_ref().map(|s| s.on_remote_header_response(io, who, response));
|
||||
}
|
||||
|
||||
fn on_remote_changes_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::RemoteChangesRequest<B::Hash>) {
|
||||
trace!(target: "sync", "Remote changes proof request {} from {} for key {} ({}..{})",
|
||||
request.id, who, request.key.to_hex(), request.first, request.last);
|
||||
let (max, proof) = match self.context_data.chain.key_changes_proof(request.first, request.last, request.max, &request.key) {
|
||||
Ok((max, proof)) => (max, proof),
|
||||
Err(error) => {
|
||||
trace!(target: "sync", "Remote changes proof request {} from {} for key {} ({}..{}) failed with: {}",
|
||||
request.id, who, request.key.to_hex(), request.first, request.last, error);
|
||||
(Zero::zero(), Default::default())
|
||||
},
|
||||
};
|
||||
self.send_message(io, who, GenericMessage::RemoteChangesResponse(message::RemoteChangesResponse {
|
||||
id: request.id, max, proof,
|
||||
}));
|
||||
}
|
||||
|
||||
fn on_remote_changes_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteChangesResponse<NumberFor<B>>) {
|
||||
trace!(target: "sync", "Remote changes proof response {} from {} (max={})",
|
||||
response.id, who, response.max);
|
||||
self.on_demand.as_ref().map(|s| s.on_remote_changes_response(io, who, response));
|
||||
}
|
||||
|
||||
|
||||
/// Execute a closure with access to a network context and specialization.
|
||||
pub fn with_spec<F, U>(&self, io: &mut SyncIo, f: F) -> U
|
||||
where F: FnOnce(&mut S, &mut Context<B>) -> U
|
||||
|
||||
@@ -23,7 +23,7 @@ use codec::{Decode, Encode};
|
||||
use hash_db::{HashDB, Hasher};
|
||||
use heapsize::HeapSizeOf;
|
||||
use substrate_trie::{Recorder, MemoryDB};
|
||||
use changes_trie::{Configuration, Storage};
|
||||
use changes_trie::{Configuration, RootsStorage, Storage};
|
||||
use changes_trie::input::{DigestIndex, ExtrinsicIndex, DigestIndexValue, ExtrinsicIndexValue};
|
||||
use changes_trie::storage::{TrieBackendAdapter, InMemoryStorage};
|
||||
use proving_backend::ProvingBackendEssence;
|
||||
@@ -44,6 +44,8 @@ pub fn key_changes<S: Storage<H>, H: Hasher>(
|
||||
key,
|
||||
roots_storage: storage,
|
||||
storage,
|
||||
begin,
|
||||
end,
|
||||
surface: surface_iterator(config, max, begin, end)?,
|
||||
|
||||
extrinsics: Default::default(),
|
||||
@@ -69,6 +71,8 @@ pub fn key_changes_proof<S: Storage<H>, H: Hasher>(
|
||||
key,
|
||||
roots_storage: storage.clone(),
|
||||
storage,
|
||||
begin,
|
||||
end,
|
||||
surface: surface_iterator(config, max, begin, end)?,
|
||||
|
||||
extrinsics: Default::default(),
|
||||
@@ -89,9 +93,9 @@ pub fn key_changes_proof<S: Storage<H>, H: Hasher>(
|
||||
|
||||
/// Check key changes proog and return changes of the key at given blocks range.
|
||||
/// `max` is the number of best known block.
|
||||
pub fn key_changes_proof_check<S: Storage<H>, H: Hasher>(
|
||||
pub fn key_changes_proof_check<S: RootsStorage<H>, H: Hasher>(
|
||||
config: &Configuration,
|
||||
roots_storage: &S, // TODO: use RootsStorage is only used to gather root
|
||||
roots_storage: &S,
|
||||
proof: Vec<Vec<u8>>,
|
||||
begin: u64,
|
||||
end: u64,
|
||||
@@ -109,6 +113,8 @@ pub fn key_changes_proof_check<S: Storage<H>, H: Hasher>(
|
||||
key,
|
||||
roots_storage,
|
||||
storage: &proof_db,
|
||||
begin,
|
||||
end,
|
||||
surface: surface_iterator(config, max, begin, end)?,
|
||||
|
||||
extrinsics: Default::default(),
|
||||
@@ -168,10 +174,12 @@ impl<'a> Iterator for SurfaceIterator<'a> {
|
||||
|
||||
/// Drilldown iterator - receives 'digest points' from surface iterator and explores
|
||||
/// every point until extrinsic is found.
|
||||
pub struct DrilldownIteratorEssence<'a, RS: 'a + Storage<H>, S: 'a + Storage<H>, H: Hasher> {
|
||||
pub struct DrilldownIteratorEssence<'a, RS: 'a + RootsStorage<H>, S: 'a + Storage<H>, H: Hasher> {
|
||||
key: &'a [u8],
|
||||
roots_storage: &'a RS,
|
||||
storage: &'a S,
|
||||
begin: u64,
|
||||
end: u64,
|
||||
surface: SurfaceIterator<'a>,
|
||||
|
||||
extrinsics: VecDeque<(u64, u32)>,
|
||||
@@ -180,7 +188,7 @@ pub struct DrilldownIteratorEssence<'a, RS: 'a + Storage<H>, S: 'a + Storage<H>,
|
||||
_hasher: ::std::marker::PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> DrilldownIteratorEssence<'a, RS, S, H> {
|
||||
impl<'a, RS: 'a + RootsStorage<H>, S: Storage<H>, H: Hasher> DrilldownIteratorEssence<'a, RS, S, H> {
|
||||
pub fn next<F>(&mut self, trie_reader: F) -> Option<Result<(u64, u32), String>>
|
||||
where
|
||||
F: FnMut(&S, H::Out, &[u8]) -> Result<Option<Vec<u8>>, String>,
|
||||
@@ -202,7 +210,17 @@ impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> DrilldownIteratorEssence
|
||||
}
|
||||
|
||||
if let Some((block, level)) = self.blocks.pop_front() {
|
||||
if let Some(trie_root) = self.roots_storage.root(block)? {
|
||||
// not having a changes trie root is an error because:
|
||||
// we never query roots for future blocks
|
||||
// AND trie roots for old blocks are known (both on full + light node)
|
||||
let trie_root = self.roots_storage.root(block)?
|
||||
.ok_or_else(|| format!("Changes trie root for block {} is not found", block))?;
|
||||
|
||||
// only return extrinsics for blocks before self.max
|
||||
// most of blocks will be filtered out beore pushing to `self.blocks`
|
||||
// here we just throwing away changes at digest blocks we're processing
|
||||
debug_assert!(block >= self.begin, "We shall not touch digests earlier than a range' begin");
|
||||
if block <= self.end {
|
||||
let extrinsics_key = ExtrinsicIndex { block, key: self.key.to_vec() }.encode();
|
||||
let extrinsics = trie_reader(&self.storage, trie_root, &extrinsics_key);
|
||||
if let Some(extrinsics) = extrinsics? {
|
||||
@@ -211,14 +229,22 @@ impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> DrilldownIteratorEssence
|
||||
self.extrinsics.extend(extrinsics.into_iter().rev().map(|e| (block, e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let blocks_key = DigestIndex { block, key: self.key.to_vec() }.encode();
|
||||
let blocks = trie_reader(&self.storage, trie_root, &blocks_key);
|
||||
if let Some(blocks) = blocks? {
|
||||
let blocks: Option<DigestIndexValue> = Decode::decode(&mut &blocks[..]);
|
||||
if let Some(blocks) = blocks {
|
||||
self.blocks.extend(blocks.into_iter().rev().map(|b| (b, level - 1)));
|
||||
}
|
||||
let blocks_key = DigestIndex { block, key: self.key.to_vec() }.encode();
|
||||
let blocks = trie_reader(&self.storage, trie_root, &blocks_key);
|
||||
if let Some(blocks) = blocks? {
|
||||
let blocks: Option<DigestIndexValue> = Decode::decode(&mut &blocks[..]);
|
||||
if let Some(blocks) = blocks {
|
||||
// filter level0 blocks here because we tend to use digest blocks,
|
||||
// AND digest block changes could also include changes for out-of-range blocks
|
||||
let begin = self.begin;
|
||||
let end = self.end;
|
||||
self.blocks.extend(blocks.into_iter()
|
||||
.rev()
|
||||
.filter(|b| level > 1 || (*b >= begin && *b <= end))
|
||||
.map(|b| (b, level - 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,11 +261,11 @@ impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> DrilldownIteratorEssence
|
||||
}
|
||||
|
||||
/// Exploring drilldown operator.
|
||||
struct DrilldownIterator<'a, RS: 'a + Storage<H>, S: 'a + Storage<H>, H: Hasher> {
|
||||
struct DrilldownIterator<'a, RS: 'a + RootsStorage<H>, S: 'a + Storage<H>, H: Hasher> {
|
||||
essence: DrilldownIteratorEssence<'a, RS, S, H>,
|
||||
}
|
||||
|
||||
impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> Iterator
|
||||
impl<'a, RS: 'a + RootsStorage<H>, S: Storage<H>, H: Hasher> Iterator
|
||||
for DrilldownIterator<'a, RS, S, H>
|
||||
where H::Out: HeapSizeOf
|
||||
{
|
||||
@@ -252,12 +278,12 @@ impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> Iterator
|
||||
}
|
||||
|
||||
/// Proving drilldown iterator.
|
||||
struct ProvingDrilldownIterator<'a, RS: 'a + Storage<H>, S: 'a + Storage<H>, H: Hasher> {
|
||||
struct ProvingDrilldownIterator<'a, RS: 'a + RootsStorage<H>, S: 'a + Storage<H>, H: Hasher> {
|
||||
essence: DrilldownIteratorEssence<'a, RS, S, H>,
|
||||
proof_recorder: RefCell<Recorder<H::Out>>,
|
||||
}
|
||||
|
||||
impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> ProvingDrilldownIterator<'a, RS, S, H> {
|
||||
impl<'a, RS: 'a + RootsStorage<H>, S: Storage<H>, H: Hasher> ProvingDrilldownIterator<'a, RS, S, H> {
|
||||
/// Consume the iterator, extracting the gathered proof in lexicographical order
|
||||
/// by value.
|
||||
pub fn extract_proof(self) -> Vec<Vec<u8>> {
|
||||
@@ -268,7 +294,7 @@ impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> ProvingDrilldownIterator
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, RS: 'a + Storage<H>, S: Storage<H>, H: Hasher> Iterator for ProvingDrilldownIterator<'a, RS, S, H> where H::Out: HeapSizeOf {
|
||||
impl<'a, RS: 'a + RootsStorage<H>, S: Storage<H>, H: Hasher> Iterator for ProvingDrilldownIterator<'a, RS, S, H> where H::Out: HeapSizeOf {
|
||||
type Item = Result<(u64, u32), String>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -401,9 +427,24 @@ mod tests {
|
||||
fn drilldown_iterator_works() {
|
||||
let (config, storage) = prepare_for_drilldown();
|
||||
let drilldown_result = key_changes::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&config, &storage, 0, 100, 1000, &[42]);
|
||||
|
||||
&config, &storage, 0, 16, 16, &[42]);
|
||||
assert_eq!(drilldown_result, Ok(vec![(8, 2), (8, 1), (6, 3), (3, 0)]));
|
||||
|
||||
let drilldown_result = key_changes::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&config, &storage, 0, 2, 4, &[42]);
|
||||
assert_eq!(drilldown_result, Ok(vec![]));
|
||||
|
||||
let drilldown_result = key_changes::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&config, &storage, 0, 3, 4, &[42]);
|
||||
assert_eq!(drilldown_result, Ok(vec![(3, 0)]));
|
||||
|
||||
let drilldown_result = key_changes::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&config, &storage, 7, 8, 8, &[42]);
|
||||
assert_eq!(drilldown_result, Ok(vec![(8, 2), (8, 1)]));
|
||||
|
||||
let drilldown_result = key_changes::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&config, &storage, 5, 7, 8, &[42]);
|
||||
assert_eq!(drilldown_result, Ok(vec![(6, 3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -433,7 +474,7 @@ mod tests {
|
||||
let (remote_config, remote_storage) = prepare_for_drilldown();
|
||||
let remote_proof = key_changes_proof::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&remote_config, &remote_storage,
|
||||
0, 100, 1000, &[42]).unwrap();
|
||||
0, 16, 16, &[42]).unwrap();
|
||||
|
||||
// happens on local light node:
|
||||
|
||||
@@ -442,7 +483,7 @@ mod tests {
|
||||
local_storage.clear_storage();
|
||||
let local_result = key_changes_proof_check::<InMemoryStorage<Blake2Hasher>, Blake2Hasher>(
|
||||
&local_config, &local_storage, remote_proof,
|
||||
0, 100, 1000, &[42]);
|
||||
0, 16, 16, &[42]);
|
||||
|
||||
// check that drilldown result is the same as if it was happening at the full node
|
||||
assert_eq!(local_result, Ok(vec![(8, 2), (8, 1), (6, 3), (3, 0)]));
|
||||
|
||||
@@ -54,10 +54,13 @@ use trie::{DBValue, trie_root};
|
||||
pub const NO_EXTRINSIC_INDEX: u32 = 0xffffffff;
|
||||
|
||||
/// Changes trie storage. Provides access to trie roots and trie nodes.
|
||||
pub trait Storage<H: Hasher>: Send + Sync {
|
||||
pub trait RootsStorage<H: Hasher>: Send + Sync {
|
||||
/// Get changes trie root for given block.
|
||||
fn root(&self, block: u64) -> Result<Option<H::Out>, String>;
|
||||
}
|
||||
|
||||
/// Changes trie storage. Provides access to trie roots and trie nodes.
|
||||
pub trait Storage<H: Hasher>: RootsStorage<H> {
|
||||
/// Get a trie node.
|
||||
fn get(&self, key: &H::Out) -> Result<Option<DBValue>, String>;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use trie::DBValue;
|
||||
use heapsize::HeapSizeOf;
|
||||
use trie::MemoryDB;
|
||||
use parking_lot::RwLock;
|
||||
use changes_trie::Storage;
|
||||
use changes_trie::{RootsStorage, Storage};
|
||||
use trie_backend_essence::TrieBackendStorage;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -94,11 +94,13 @@ impl<H: Hasher> InMemoryStorage<H> where H::Out: HeapSizeOf {
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> Storage<H> for InMemoryStorage<H> where H::Out: HeapSizeOf {
|
||||
impl<H: Hasher> RootsStorage<H> for InMemoryStorage<H> where H::Out: HeapSizeOf {
|
||||
fn root(&self, block: u64) -> Result<Option<H::Out>, String> {
|
||||
Ok(self.data.read().roots.get(&block).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> Storage<H> for InMemoryStorage<H> where H::Out: HeapSizeOf {
|
||||
fn get(&self, key: &H::Out) -> Result<Option<DBValue>, String> {
|
||||
MemoryDB::<H>::get(&self.data.read().mdb, key)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ pub use testing::TestExternalities;
|
||||
pub use ext::Ext;
|
||||
pub use backend::Backend;
|
||||
pub use changes_trie::{Storage as ChangesTrieStorage,
|
||||
RootsStorage as ChangesTrieRootsStorage,
|
||||
InMemoryStorage as InMemoryChangesTrieStorage,
|
||||
key_changes, key_changes_proof, key_changes_proof_check};
|
||||
pub use overlayed_changes::OverlayedChanges;
|
||||
@@ -267,12 +268,15 @@ where
|
||||
// proof-of-execution on light clients. And the proof is recorded by the backend which
|
||||
// is created after OverlayedChanges
|
||||
|
||||
let changes_trie_config = try_read_overlay_value(
|
||||
overlay,
|
||||
backend,
|
||||
well_known_keys::CHANGES_TRIE_CONFIG
|
||||
)?;
|
||||
set_changes_trie_config(overlay, changes_trie_config)?;
|
||||
let init_overlay = |overlay: &mut OverlayedChanges, final_check: bool| {
|
||||
let changes_trie_config = try_read_overlay_value(
|
||||
overlay,
|
||||
backend,
|
||||
well_known_keys::CHANGES_TRIE_CONFIG
|
||||
)?;
|
||||
set_changes_trie_config(overlay, changes_trie_config, final_check)
|
||||
};
|
||||
init_overlay(overlay, false)?;
|
||||
|
||||
let result = {
|
||||
let mut orig_prospective = overlay.prospective.clone();
|
||||
@@ -334,6 +338,11 @@ where
|
||||
result.map(move |out| (out, storage_delta, changes_delta))
|
||||
};
|
||||
|
||||
// ensure that changes trie config has not been changed
|
||||
if result.is_ok() {
|
||||
init_overlay(overlay, true)?;
|
||||
}
|
||||
|
||||
result.map_err(|e| Box::new(e) as _)
|
||||
}
|
||||
|
||||
@@ -429,18 +438,22 @@ where
|
||||
|
||||
/// 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>>) -> Result<(), Box<Error>> {
|
||||
pub(crate) fn set_changes_trie_config(overlay: &mut OverlayedChanges, config: Option<Vec<u8>>, final_check: bool) -> Result<(), Box<Error>> {
|
||||
let config = match config {
|
||||
Some(v) => Some(changes_trie::Configuration::decode(&mut &v[..])
|
||||
.ok_or_else(|| Box::new("Failed to decode changes trie configuration".to_owned()) as Box<Error>)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
if final_check && overlay.changes_trie_config.is_some() != config.is_some() {
|
||||
return Err(Box::new("Changes trie configuration change is not supported".to_owned()));
|
||||
}
|
||||
|
||||
if let Some(config) = config {
|
||||
if !overlay.set_changes_trie_config(config) {
|
||||
return Err(Box::new("Changes trie configuration change is not supported".to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -462,13 +475,18 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use codec::Encode;
|
||||
use super::*;
|
||||
use super::backend::InMemory;
|
||||
use super::ext::Ext;
|
||||
use super::changes_trie::InMemoryStorage as InMemoryChangesTrieStorage;
|
||||
use super::changes_trie::{
|
||||
InMemoryStorage as InMemoryChangesTrieStorage,
|
||||
Configuration as ChangesTrieConfig,
|
||||
};
|
||||
use primitives::{Blake2Hasher};
|
||||
|
||||
struct DummyCodeExecutor {
|
||||
change_changes_trie_config: bool,
|
||||
native_available: bool,
|
||||
native_succeeds: bool,
|
||||
fallback_succeeds: bool,
|
||||
@@ -486,6 +504,13 @@ mod tests {
|
||||
_data: &[u8],
|
||||
use_native: bool
|
||||
) -> (Result<Vec<u8>, Self::Error>, bool) {
|
||||
if self.change_changes_trie_config {
|
||||
ext.place_storage(well_known_keys::CHANGES_TRIE_CONFIG.to_vec(), Some(ChangesTrieConfig {
|
||||
digest_interval: 777,
|
||||
digest_levels: 333,
|
||||
}.encode()));
|
||||
}
|
||||
|
||||
let using_native = use_native && self.native_available;
|
||||
match (using_native, self.native_succeeds, self.fallback_succeeds) {
|
||||
(true, true, _) | (false, _, true) =>
|
||||
@@ -510,6 +535,7 @@ mod tests {
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: false,
|
||||
native_available: true,
|
||||
native_succeeds: true,
|
||||
fallback_succeeds: true,
|
||||
@@ -528,6 +554,7 @@ mod tests {
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: false,
|
||||
native_available: true,
|
||||
native_succeeds: true,
|
||||
fallback_succeeds: false,
|
||||
@@ -546,6 +573,7 @@ mod tests {
|
||||
#[test]
|
||||
fn prove_execution_and_proof_check_works() {
|
||||
let executor = DummyCodeExecutor {
|
||||
change_changes_trie_config: false,
|
||||
native_available: true,
|
||||
native_succeeds: true,
|
||||
fallback_succeeds: true,
|
||||
@@ -621,4 +649,22 @@ mod tests {
|
||||
assert_eq!(local_result1, Some(vec![24]));
|
||||
assert_eq!(local_result2, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_change_changes_trie_config() {
|
||||
assert!(execute(
|
||||
&trie_backend::tests::test_trie(),
|
||||
Some(&InMemoryChangesTrieStorage::new()),
|
||||
&mut Default::default(),
|
||||
&DummyCodeExecutor {
|
||||
change_changes_trie_config: true,
|
||||
native_available: false,
|
||||
native_succeeds: true,
|
||||
fallback_succeeds: true,
|
||||
},
|
||||
"test",
|
||||
&[],
|
||||
ExecutionStrategy::NativeWhenPossible
|
||||
).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ impl OverlayedChanges {
|
||||
///
|
||||
/// Returns false if configuration has been set already and we now trying
|
||||
/// to install different configuration. This isn't supported now.
|
||||
#[must_use = "Result must be checked"]
|
||||
pub(crate) fn set_changes_trie_config(&mut self, config: ChangesTrieConfig) -> bool {
|
||||
if let Some(ref old_config) = self.changes_trie_config {
|
||||
// we do not support changes trie configuration' change now
|
||||
|
||||
@@ -39,8 +39,9 @@ impl<H: Hasher> TestExternalities<H> where H::Out: HeapSizeOf {
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
super::set_changes_trie_config(
|
||||
&mut overlay,
|
||||
inner.get(&CHANGES_TRIE_CONFIG.to_vec()).cloned())
|
||||
.expect("changes trie configuration is correct in test env; qed");
|
||||
inner.get(&CHANGES_TRIE_CONFIG.to_vec()).cloned(),
|
||||
false,
|
||||
).expect("changes trie configuration is correct in test env; qed");
|
||||
|
||||
TestExternalities {
|
||||
inner,
|
||||
|
||||
@@ -38,9 +38,9 @@ pub trait TestClient {
|
||||
}
|
||||
|
||||
impl<B, E> TestClient for Client<B, E, runtime::Block>
|
||||
where
|
||||
B: client::backend::Backend<runtime::Block, Blake2Hasher>,
|
||||
E: client::CallExecutor<runtime::Block, Blake2Hasher>
|
||||
where
|
||||
B: client::backend::Backend<runtime::Block, Blake2Hasher>,
|
||||
E: client::CallExecutor<runtime::Block, Blake2Hasher>
|
||||
{
|
||||
fn justify_and_import(&self, origin: client::BlockOrigin, block: runtime::Block) -> client::error::Result<()> {
|
||||
let authorities: [ed25519::Pair; 3] = [
|
||||
|
||||
@@ -68,29 +68,37 @@ pub type Executor = client::LocalCallExecutor<
|
||||
|
||||
/// Creates new client instance used for tests.
|
||||
pub fn new() -> client::Client<Backend, Executor, runtime::Block> {
|
||||
new_with_backend(Arc::new(Backend::new()))
|
||||
new_with_backend(Arc::new(Backend::new()), false)
|
||||
}
|
||||
|
||||
/// Creates new test client instance that suports changes trie creation.
|
||||
pub fn new_with_changes_trie() -> client::Client<Backend, Executor, runtime::Block> {
|
||||
new_with_backend(Arc::new(Backend::new()), true)
|
||||
}
|
||||
|
||||
/// Creates new client instance used for tests with an explicitely provided backend.
|
||||
/// This is useful for testing backend implementations.
|
||||
pub fn new_with_backend<B>(backend: Arc<B>) -> client::Client<B, client::LocalCallExecutor<B, executor::NativeExecutor<LocalExecutor>>, runtime::Block>
|
||||
pub fn new_with_backend<B>(
|
||||
backend: Arc<B>,
|
||||
support_changes_trie: bool
|
||||
) -> client::Client<B, client::LocalCallExecutor<B, executor::NativeExecutor<LocalExecutor>>, runtime::Block>
|
||||
where
|
||||
B: backend::LocalBackend<runtime::Block, Blake2Hasher>,
|
||||
{
|
||||
let executor = NativeExecutor::new();
|
||||
client::new_with_backend(backend, executor, genesis_storage()).unwrap()
|
||||
client::new_with_backend(backend, executor, genesis_storage(support_changes_trie)).unwrap()
|
||||
}
|
||||
|
||||
fn genesis_config() -> GenesisConfig {
|
||||
GenesisConfig::new_simple(vec![
|
||||
fn genesis_config(support_changes_trie: bool) -> GenesisConfig {
|
||||
GenesisConfig::new(support_changes_trie, vec![
|
||||
Keyring::Alice.to_raw_public().into(),
|
||||
Keyring::Bob.to_raw_public().into(),
|
||||
Keyring::Charlie.to_raw_public().into(),
|
||||
], 1000)
|
||||
}
|
||||
|
||||
fn genesis_storage() -> StorageMap {
|
||||
let mut storage = genesis_config().genesis_map();
|
||||
fn genesis_storage(support_changes_trie: bool) -> StorageMap {
|
||||
let mut storage = genesis_config(support_changes_trie).genesis_map();
|
||||
let block: runtime::Block = client::genesis::construct_genesis_block(&storage);
|
||||
storage.extend(additional_storage_with_genesis(&block));
|
||||
storage
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn test_leaves_for_backend<B>(backend: Arc<B>) where
|
||||
// B2 -> C3
|
||||
// A1 -> D2
|
||||
|
||||
let client = ::new_with_backend(backend.clone());
|
||||
let client = ::new_with_backend(backend.clone(), false);
|
||||
|
||||
let genesis_hash = client.info().unwrap().chain.genesis_hash;
|
||||
|
||||
@@ -153,7 +153,7 @@ pub fn test_blockchain_query_by_number_gets_canonical<B>(backend: Arc<B>) where
|
||||
// A1 -> B2 -> B3 -> B4
|
||||
// B2 -> C3
|
||||
// A1 -> D2
|
||||
let client = ::new_with_backend(backend);
|
||||
let client = ::new_with_backend(backend, false);
|
||||
|
||||
// G -> A1
|
||||
let a1 = client.new_block().unwrap().bake().unwrap();
|
||||
|
||||
@@ -18,20 +18,29 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use runtime_io::twox_128;
|
||||
use codec::{KeyedVec, Joiner};
|
||||
use primitives::AuthorityId;
|
||||
use codec::{Encode, KeyedVec, Joiner};
|
||||
use primitives::{AuthorityId, ChangesTrieConfiguration};
|
||||
use primitives::storage::well_known_keys;
|
||||
use runtime_primitives::traits::Block;
|
||||
|
||||
/// Configuration of a general Substrate test genesis block.
|
||||
pub struct GenesisConfig {
|
||||
pub changes_trie_config: Option<ChangesTrieConfiguration>,
|
||||
pub authorities: Vec<AuthorityId>,
|
||||
pub balances: Vec<(AuthorityId, u64)>,
|
||||
}
|
||||
|
||||
impl GenesisConfig {
|
||||
pub fn new_simple(authorities: Vec<AuthorityId>, balance: u64) -> Self {
|
||||
Self::new(false, authorities, balance)
|
||||
}
|
||||
|
||||
pub fn new(support_changes_trie: bool, authorities: Vec<AuthorityId>, balance: u64) -> Self {
|
||||
GenesisConfig {
|
||||
changes_trie_config: match support_changes_trie {
|
||||
true => Some(super::changes_trie_config()),
|
||||
false => None,
|
||||
},
|
||||
authorities: authorities.clone(),
|
||||
balances: authorities.into_iter().map(|a| (a, balance)).collect(),
|
||||
}
|
||||
@@ -39,7 +48,7 @@ impl GenesisConfig {
|
||||
|
||||
pub fn genesis_map(&self) -> HashMap<Vec<u8>, Vec<u8>> {
|
||||
let wasm_runtime = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm").to_vec();
|
||||
self.balances.iter()
|
||||
let mut map: HashMap<Vec<u8>, Vec<u8>> = self.balances.iter()
|
||||
.map(|&(account, balance)| (account.to_keyed_vec(b"balance:"), vec![].and(&balance)))
|
||||
.map(|(k, v)| (twox_128(&k[..])[..].to_vec(), v.to_vec()))
|
||||
.chain(vec![
|
||||
@@ -51,7 +60,11 @@ impl GenesisConfig {
|
||||
.enumerate()
|
||||
.map(|(i, account)| ((i as u32).to_keyed_vec(well_known_keys::AUTHORITY_PREFIX), vec![].and(account)))
|
||||
)
|
||||
.collect()
|
||||
.collect();
|
||||
if let Some(ref changes_trie_config) = self.changes_trie_config {
|
||||
map.insert(well_known_keys::CHANGES_TRIE_CONFIG.to_vec(), changes_trie_config.encode());
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,14 @@ pub fn run_tests(mut input: &[u8]) -> Vec<u8> {
|
||||
[stxs.len() as u8].encode()
|
||||
}
|
||||
|
||||
/// Changes trie configuration (optionally) used in tests.
|
||||
pub fn changes_trie_config() -> primitives::ChangesTrieConfiguration {
|
||||
primitives::ChangesTrieConfiguration {
|
||||
digest_interval: 4,
|
||||
digest_levels: 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod api {
|
||||
use system;
|
||||
impl_stubs!(
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! and depositing logs.
|
||||
|
||||
use rstd::prelude::*;
|
||||
use runtime_io::{storage_root, enumerated_trie_root};
|
||||
use runtime_io::{storage_root, enumerated_trie_root, storage_changes_root};
|
||||
use runtime_support::storage::{self, StorageValue, StorageMap};
|
||||
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256, Digest as DigestT};
|
||||
use runtime_primitives::generic;
|
||||
@@ -32,15 +32,18 @@ const NONCE_OF: &[u8] = b"nonce:";
|
||||
const BALANCE_OF: &[u8] = b"balance:";
|
||||
|
||||
storage_items! {
|
||||
ExtrinsicIndex: b"sys:xti" => required u32;
|
||||
ExtrinsicData: b"sys:xtd" => required map [ u32 => Vec<u8> ];
|
||||
// The current block number being processed. Set by `execute_block`.
|
||||
Number: b"sys:num" => required BlockNumber;
|
||||
ParentHash: b"sys:pha" => required Hash;
|
||||
}
|
||||
|
||||
pub fn balance_of_key(who: AccountId) -> Vec<u8> {
|
||||
who.to_keyed_vec(BALANCE_OF)
|
||||
}
|
||||
|
||||
pub fn balance_of(who: AccountId) -> u64 {
|
||||
storage::get_or(&who.to_keyed_vec(BALANCE_OF), 0)
|
||||
storage::get_or(&balance_of_key(who), 0)
|
||||
}
|
||||
|
||||
pub fn nonce_of(who: AccountId) -> u64 {
|
||||
@@ -62,7 +65,7 @@ pub fn initialise_block(header: Header) {
|
||||
// populate environment.
|
||||
<Number>::put(&header.number);
|
||||
<ParentHash>::put(&header.parent_hash);
|
||||
<ExtrinsicIndex>::put(0);
|
||||
storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32);
|
||||
}
|
||||
|
||||
/// Actually execute all transitioning for `block`.
|
||||
@@ -77,26 +80,38 @@ pub fn execute_block(block: Block) {
|
||||
assert!(txs_root == header.extrinsics_root, "Transaction trie root must be valid.");
|
||||
|
||||
// execute transactions
|
||||
block.extrinsics.iter().for_each(|e| { execute_transaction_backend(e).map_err(|_| ()).expect("Extrinsic error"); });
|
||||
block.extrinsics.iter().enumerate().for_each(|(i, e)| {
|
||||
storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &(i as u32));
|
||||
execute_transaction_backend(e).map_err(|_| ()).expect("Extrinsic error");
|
||||
storage::unhashed::kill(well_known_keys::EXTRINSIC_INDEX);
|
||||
});
|
||||
|
||||
// check storage root.
|
||||
let storage_root = storage_root().into();
|
||||
info_expect_equal_hash(&storage_root, &header.state_root);
|
||||
assert!(storage_root == header.state_root, "Storage root must match that calculated.");
|
||||
|
||||
// check digest
|
||||
let mut digest = Digest::default();
|
||||
if let Some(storage_changes_root) = storage_changes_root(header.number) {
|
||||
digest.push(generic::DigestItem::ChangesTrieRoot::<Hash, u64>(storage_changes_root.into()));
|
||||
}
|
||||
assert!(digest == header.digest, "Header digest items must match that calculated.");
|
||||
}
|
||||
|
||||
/// Execute a transaction outside of the block execution function.
|
||||
/// This doesn't attempt to validate anything regarding the block.
|
||||
pub fn execute_transaction(utx: Extrinsic) -> ApplyResult {
|
||||
let extrinsic_index = ExtrinsicIndex::get();
|
||||
let extrinsic_index: u32 = storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX).unwrap();
|
||||
let result = execute_transaction_backend(&utx);
|
||||
ExtrinsicData::insert(extrinsic_index, utx.encode());
|
||||
ExtrinsicIndex::put(extrinsic_index + 1);
|
||||
execute_transaction_backend(&utx)
|
||||
storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &(extrinsic_index + 1));
|
||||
result
|
||||
}
|
||||
|
||||
/// Finalise the block.
|
||||
pub fn finalise_block() -> Header {
|
||||
let extrinsic_index = ExtrinsicIndex::take();
|
||||
let extrinsic_index: u32 = storage::unhashed::take(well_known_keys::EXTRINSIC_INDEX).unwrap();
|
||||
let txs: Vec<_> = (0..extrinsic_index).map(ExtrinsicData::take).collect();
|
||||
let txs = txs.iter().map(Vec::as_slice).collect::<Vec<_>>();
|
||||
let extrinsics_root = enumerated_trie_root::<Blake2Hasher>(&txs).into();
|
||||
|
||||
Reference in New Issue
Block a user