mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 08:51:09 +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
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user