mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-22 16:11:08 +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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user