mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 01:15:47 +00:00
Repotted client
This commit is contained in:
@@ -0,0 +1,410 @@
|
|||||||
|
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Substrate Client
|
||||||
|
|
||||||
|
use primitives::{block, AuthorityId};
|
||||||
|
use primitives::storage::{StorageKey, StorageData};
|
||||||
|
use codec::{KeyedVec, Slicable};
|
||||||
|
use state_machine::{self, Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor};
|
||||||
|
|
||||||
|
use backend::{self, BlockImportOperation};
|
||||||
|
use blockchain::{self, Info as ChainInfo, BlockId, Backend as ChainBackend};
|
||||||
|
use {error, in_mem, block_builder, runtime_io};
|
||||||
|
|
||||||
|
/// Polkadot Client
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Client<B, E> where B: backend::Backend {
|
||||||
|
backend: B,
|
||||||
|
executor: E,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client info
|
||||||
|
// TODO: split queue info from chain info and amalgamate into single struct.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ClientInfo {
|
||||||
|
/// Best block hash.
|
||||||
|
pub chain: ChainInfo,
|
||||||
|
/// Best block number in the queue.
|
||||||
|
pub best_queued_number: Option<block::Number>,
|
||||||
|
/// Best queued block hash.
|
||||||
|
pub best_queued_hash: Option<block::HeaderHash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information regarding the result of a call.
|
||||||
|
pub struct CallResult {
|
||||||
|
/// The data that was returned from the call.
|
||||||
|
pub return_data: Vec<u8>,
|
||||||
|
/// The changes made to the state by the call.
|
||||||
|
pub changes: OverlayedChanges,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block import result.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ImportResult {
|
||||||
|
/// Added to the import queue.
|
||||||
|
Queued,
|
||||||
|
/// Already in the import queue.
|
||||||
|
AlreadyQueued,
|
||||||
|
/// Already in the blockchain.
|
||||||
|
AlreadyInChain,
|
||||||
|
/// Block or parent is known to be bad.
|
||||||
|
KnownBad,
|
||||||
|
/// Block parent is not in the chain.
|
||||||
|
UnknownParent,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block status.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum BlockStatus {
|
||||||
|
/// Added to the import queue.
|
||||||
|
Queued,
|
||||||
|
/// Already in the blockchain.
|
||||||
|
InChain,
|
||||||
|
/// Block or parent is known to be bad.
|
||||||
|
KnownBad,
|
||||||
|
/// Not in the queue or the blockchain.
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an instance of in-memory client.
|
||||||
|
pub fn new_in_mem<E, F>(
|
||||||
|
executor: E,
|
||||||
|
build_genesis: F
|
||||||
|
) -> error::Result<Client<in_mem::Backend, E>>
|
||||||
|
where
|
||||||
|
E: CodeExecutor,
|
||||||
|
F: FnOnce() -> (block::Header, Vec<(Vec<u8>, Vec<u8>)>)
|
||||||
|
{
|
||||||
|
Client::new(in_mem::Backend::new(), executor, build_genesis)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, E> Client<B, E> where
|
||||||
|
B: backend::Backend,
|
||||||
|
E: CodeExecutor,
|
||||||
|
error::Error: From<<<B as backend::Backend>::State as StateBackend>::Error>,
|
||||||
|
{
|
||||||
|
/// Creates new Polkadot Client with given blockchain and code executor.
|
||||||
|
pub fn new<F>(
|
||||||
|
backend: B,
|
||||||
|
executor: E,
|
||||||
|
build_genesis: F
|
||||||
|
) -> error::Result<Self>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> (block::Header, Vec<(Vec<u8>, Vec<u8>)>)
|
||||||
|
{
|
||||||
|
if backend.blockchain().header(BlockId::Number(0))?.is_none() {
|
||||||
|
trace!("Empty database, writing genesis block");
|
||||||
|
let (genesis_header, genesis_store) = build_genesis();
|
||||||
|
let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?;
|
||||||
|
op.reset_storage(genesis_store.into_iter())?;
|
||||||
|
op.set_block_data(genesis_header, Some(vec![]), true)?;
|
||||||
|
backend.commit_operation(op)?;
|
||||||
|
}
|
||||||
|
Ok(Client {
|
||||||
|
backend,
|
||||||
|
executor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the state at a given block.
|
||||||
|
pub fn state_at(&self, block: &BlockId) -> error::Result<B::State> {
|
||||||
|
self.backend.state_at(*block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expose backend reference. To be used in tests only
|
||||||
|
pub fn backend(&self) -> &B {
|
||||||
|
&self.backend
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return single storage entry of contract under given address in state in a block of given hash.
|
||||||
|
pub fn storage(&self, id: &BlockId, key: &StorageKey) -> error::Result<StorageData> {
|
||||||
|
Ok(self.state_at(id)?
|
||||||
|
.storage(&key.0)
|
||||||
|
.map(|x| StorageData(x.to_vec()))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the code at a given block.
|
||||||
|
pub fn code_at(&self, id: &BlockId) -> error::Result<Vec<u8>> {
|
||||||
|
self.storage(id, &StorageKey(b":code".to_vec())).map(|data| data.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone a new instance of Executor.
|
||||||
|
pub fn clone_executor(&self) -> E where E: Clone {
|
||||||
|
self.executor.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current set of authorities from storage.
|
||||||
|
pub fn authorities_at(&self, id: &BlockId) -> error::Result<Vec<AuthorityId>> {
|
||||||
|
let state = self.state_at(id)?;
|
||||||
|
(0..u32::decode(&mut state.storage(b":auth:len")?).ok_or(error::ErrorKind::AuthLen)?)
|
||||||
|
.map(|i| state.storage(&i.to_keyed_vec(b":auth:"))
|
||||||
|
.map_err(|_| error::ErrorKind::Backend)
|
||||||
|
.and_then(|mut s| AuthorityId::decode(&mut s).ok_or(error::ErrorKind::Auth(i)))
|
||||||
|
.map_err(Into::into)
|
||||||
|
).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a call to a contract on top of state in a block of given hash.
|
||||||
|
///
|
||||||
|
/// No changes are made.
|
||||||
|
pub fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result<CallResult> {
|
||||||
|
let mut changes = OverlayedChanges::default();
|
||||||
|
let return_data = state_machine::execute(
|
||||||
|
&self.state_at(id)?,
|
||||||
|
&mut changes,
|
||||||
|
&self.executor,
|
||||||
|
method,
|
||||||
|
call_data,
|
||||||
|
)?;
|
||||||
|
Ok(CallResult { return_data, changes })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up the native execution environment to call into a native runtime code.
|
||||||
|
pub fn using_environment<F: FnOnce() -> T, T>(
|
||||||
|
&self, f: F
|
||||||
|
) -> error::Result<T> {
|
||||||
|
self.using_environment_at(&BlockId::Number(self.info()?.chain.best_number), &mut Default::default(), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up the native execution environment to call into a native runtime code.
|
||||||
|
pub fn using_environment_at<F: FnOnce() -> T, T>(
|
||||||
|
&self,
|
||||||
|
id: &BlockId,
|
||||||
|
overlay: &mut OverlayedChanges,
|
||||||
|
f: F
|
||||||
|
) -> error::Result<T> {
|
||||||
|
Ok(runtime_io::with_externalities(&mut Ext { backend: &self.state_at(id)?, overlay }, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new block, built on the head of the chain.
|
||||||
|
pub fn new_block(&self) -> error::Result<block_builder::BlockBuilder<B, E>> where E: Clone {
|
||||||
|
block_builder::BlockBuilder::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new block, built on top of `parent`.
|
||||||
|
pub fn new_block_at(&self, parent: &BlockId) -> error::Result<block_builder::BlockBuilder<B, E>> where E: Clone {
|
||||||
|
block_builder::BlockBuilder::at_block(parent, &self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queue a block for import.
|
||||||
|
pub fn import_block(&self, header: block::Header, body: Option<block::Body>) -> error::Result<ImportResult> {
|
||||||
|
// TODO: import lock
|
||||||
|
// TODO: validate block
|
||||||
|
match self.backend.blockchain().status(BlockId::Hash(header.parent_hash))? {
|
||||||
|
blockchain::BlockStatus::InChain => (),
|
||||||
|
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut transaction = self.backend.begin_operation(BlockId::Hash(header.parent_hash))?;
|
||||||
|
let mut overlay = OverlayedChanges::default();
|
||||||
|
|
||||||
|
state_machine::execute(
|
||||||
|
transaction.state()?,
|
||||||
|
&mut overlay,
|
||||||
|
&self.executor,
|
||||||
|
"execute_block",
|
||||||
|
&block::Block { header: header.clone(), transactions: body.clone().unwrap_or_default().clone() }.encode()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let is_new_best = header.number == self.backend.blockchain().info()?.best_number + 1;
|
||||||
|
transaction.set_block_data(header, body, is_new_best)?;
|
||||||
|
transaction.set_storage(overlay.drain())?;
|
||||||
|
self.backend.commit_operation(transaction)?;
|
||||||
|
Ok(ImportResult::Queued)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get blockchain info.
|
||||||
|
pub fn info(&self) -> error::Result<ClientInfo> {
|
||||||
|
let info = self.backend.blockchain().info().map_err(|e| error::Error::from_blockchain(Box::new(e)))?;
|
||||||
|
Ok(ClientInfo {
|
||||||
|
chain: info,
|
||||||
|
best_queued_hash: None,
|
||||||
|
best_queued_number: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block status.
|
||||||
|
pub fn block_status(&self, id: &BlockId) -> error::Result<BlockStatus> {
|
||||||
|
// TODO: more efficient implementation
|
||||||
|
match self.backend.blockchain().header(*id).map_err(|e| error::Error::from_blockchain(Box::new(e)))?.is_some() {
|
||||||
|
true => Ok(BlockStatus::InChain),
|
||||||
|
false => Ok(BlockStatus::Unknown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block hash by number.
|
||||||
|
pub fn block_hash(&self, block_number: block::Number) -> error::Result<Option<block::HeaderHash>> {
|
||||||
|
self.backend.blockchain().hash(block_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an arbitrary block ID into a block hash.
|
||||||
|
pub fn block_hash_from_id(&self, id: &BlockId) -> error::Result<Option<block::HeaderHash>> {
|
||||||
|
match *id {
|
||||||
|
BlockId::Hash(h) => Ok(Some(h)),
|
||||||
|
BlockId::Number(n) => self.block_hash(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an arbitrary block ID into a block hash.
|
||||||
|
pub fn block_number_from_id(&self, id: &BlockId) -> error::Result<Option<block::Number>> {
|
||||||
|
match *id {
|
||||||
|
BlockId::Hash(_) => Ok(self.header(id)?.map(|h| h.number)),
|
||||||
|
BlockId::Number(n) => Ok(Some(n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block header by id.
|
||||||
|
pub fn header(&self, id: &BlockId) -> error::Result<Option<block::Header>> {
|
||||||
|
self.backend.blockchain().header(*id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get block body by id.
|
||||||
|
pub fn body(&self, id: &BlockId) -> error::Result<Option<block::Body>> {
|
||||||
|
self.backend.blockchain().body(*id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use codec::Slicable;
|
||||||
|
use keyring::Keyring;
|
||||||
|
use {primitives, genesis};
|
||||||
|
use primitives::block::Transaction as PrimitiveTransaction;
|
||||||
|
use test_runtime::genesismap::{GenesisConfig, additional_storage_with_genesis};
|
||||||
|
use test_runtime::{UncheckedTransaction, Transaction};
|
||||||
|
use test_runtime;
|
||||||
|
|
||||||
|
native_executor_instance!(Executor, test_runtime::api::dispatch, include_bytes!("../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm"));
|
||||||
|
|
||||||
|
fn genesis_config() -> GenesisConfig {
|
||||||
|
GenesisConfig::new_simple(vec![
|
||||||
|
Keyring::Alice.to_raw_public(),
|
||||||
|
Keyring::Bob.to_raw_public(),
|
||||||
|
Keyring::Charlie.to_raw_public()
|
||||||
|
], 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_genesis() -> (primitives::block::Header, Vec<(Vec<u8>, Vec<u8>)>) {
|
||||||
|
let mut storage = genesis_config().genesis_map();
|
||||||
|
let block = genesis::construct_genesis_block(&storage);
|
||||||
|
storage.extend(additional_storage_with_genesis(&block));
|
||||||
|
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_initialises_from_genesis_ok() {
|
||||||
|
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
||||||
|
let genesis_hash = client.block_hash(0).unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), genesis_hash);
|
||||||
|
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Alice.to_raw_public())).unwrap(), 1000);
|
||||||
|
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Ferdie.to_raw_public())).unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authorities_call_works() {
|
||||||
|
let genesis_config = GenesisConfig::new_simple(vec![
|
||||||
|
Keyring::Alice.to_raw_public(),
|
||||||
|
Keyring::Bob.to_raw_public(),
|
||||||
|
Keyring::Charlie.to_raw_public()
|
||||||
|
], 1000);
|
||||||
|
|
||||||
|
let prepare_genesis = || {
|
||||||
|
let mut storage = genesis_config.genesis_map();
|
||||||
|
let block = genesis::construct_genesis_block(&storage);
|
||||||
|
storage.extend(additional_storage_with_genesis(&block));
|
||||||
|
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
||||||
|
};
|
||||||
|
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(client.info().unwrap().chain.best_number, 0);
|
||||||
|
assert_eq!(client.authorities_at(&BlockId::Number(0)).unwrap(), vec![
|
||||||
|
Keyring::Alice.to_raw_public(),
|
||||||
|
Keyring::Bob.to_raw_public(),
|
||||||
|
Keyring::Charlie.to_raw_public()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_builder_works_with_no_transactions() {
|
||||||
|
let genesis_config = GenesisConfig::new_simple(vec![
|
||||||
|
Keyring::Alice.to_raw_public(),
|
||||||
|
Keyring::Bob.to_raw_public(),
|
||||||
|
Keyring::Charlie.to_raw_public()
|
||||||
|
], 1000);
|
||||||
|
|
||||||
|
let prepare_genesis = || {
|
||||||
|
let mut storage = genesis_config.genesis_map();
|
||||||
|
let block = genesis::construct_genesis_block(&storage);
|
||||||
|
storage.extend(additional_storage_with_genesis(&block));
|
||||||
|
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
||||||
|
};
|
||||||
|
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
||||||
|
|
||||||
|
let builder = client.new_block().unwrap();
|
||||||
|
let block = builder.bake().unwrap();
|
||||||
|
|
||||||
|
client.import_block(block.header, Some(block.transactions)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||||
|
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), client.block_hash(1).unwrap().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Signable {
|
||||||
|
fn signed(self) -> PrimitiveTransaction;
|
||||||
|
}
|
||||||
|
impl Signable for Transaction {
|
||||||
|
fn signed(self) -> PrimitiveTransaction {
|
||||||
|
let signature = Keyring::from_raw_public(self.from.clone()).unwrap().sign(&self.encode());
|
||||||
|
PrimitiveTransaction::decode(&mut UncheckedTransaction { signature, tx: self }.encode().as_ref()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_builder_works_with_transactions() {
|
||||||
|
let genesis_config = GenesisConfig::new_simple(vec![
|
||||||
|
Keyring::Alice.to_raw_public(),
|
||||||
|
Keyring::Bob.to_raw_public(),
|
||||||
|
Keyring::Charlie.to_raw_public()
|
||||||
|
], 1000);
|
||||||
|
|
||||||
|
let prepare_genesis = || {
|
||||||
|
let mut storage = genesis_config.genesis_map();
|
||||||
|
let block = genesis::construct_genesis_block(&storage);
|
||||||
|
storage.extend(additional_storage_with_genesis(&block));
|
||||||
|
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
||||||
|
};
|
||||||
|
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
||||||
|
|
||||||
|
let mut builder = client.new_block().unwrap();
|
||||||
|
|
||||||
|
builder.push(Transaction {
|
||||||
|
from: Keyring::Alice.to_raw_public(),
|
||||||
|
to: Keyring::Ferdie.to_raw_public(),
|
||||||
|
amount: 42,
|
||||||
|
nonce: 0
|
||||||
|
}.signed()).unwrap();
|
||||||
|
let block = builder.bake().unwrap();
|
||||||
|
client.import_block(block.header, Some(block.transactions)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||||
|
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
|
||||||
|
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Alice.to_raw_public())).unwrap(), 958);
|
||||||
|
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Ferdie.to_raw_public())).unwrap(), 42);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Substrate Client
|
//! Substrate Client and associated logic.
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
@@ -40,398 +40,7 @@ pub mod backend;
|
|||||||
pub mod in_mem;
|
pub mod in_mem;
|
||||||
pub mod genesis;
|
pub mod genesis;
|
||||||
pub mod block_builder;
|
pub mod block_builder;
|
||||||
|
mod client;
|
||||||
|
|
||||||
pub use blockchain::Info as ChainInfo;
|
pub use client::{Client, ClientInfo, CallResult, ImportResult, BlockStatus, new_in_mem};
|
||||||
pub use blockchain::BlockId;
|
pub use blockchain::{Info as ChainInfo, BlockId};
|
||||||
|
|
||||||
use primitives::{block, AuthorityId};
|
|
||||||
use primitives::storage::{StorageKey, StorageData};
|
|
||||||
use codec::{KeyedVec, Slicable};
|
|
||||||
|
|
||||||
use blockchain::Backend as BlockchainBackend;
|
|
||||||
use backend::BlockImportOperation;
|
|
||||||
use state_machine::backend::Backend as StateBackend;
|
|
||||||
use state_machine::{Ext, OverlayedChanges};
|
|
||||||
|
|
||||||
/// Polkadot Client
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client<B, E> where B: backend::Backend {
|
|
||||||
backend: B,
|
|
||||||
executor: E,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Client info
|
|
||||||
// TODO: split queue info from chain info and amalgamate into single struct.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ClientInfo {
|
|
||||||
/// Best block hash.
|
|
||||||
pub chain: ChainInfo,
|
|
||||||
/// Best block number in the queue.
|
|
||||||
pub best_queued_number: Option<block::Number>,
|
|
||||||
/// Best queued block hash.
|
|
||||||
pub best_queued_hash: Option<block::HeaderHash>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Information regarding the result of a call.
|
|
||||||
pub struct CallResult {
|
|
||||||
/// The data that was returned from the call.
|
|
||||||
pub return_data: Vec<u8>,
|
|
||||||
/// The changes made to the state by the call.
|
|
||||||
pub changes: state_machine::OverlayedChanges,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Block import result.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ImportResult {
|
|
||||||
/// Added to the import queue.
|
|
||||||
Queued,
|
|
||||||
/// Already in the import queue.
|
|
||||||
AlreadyQueued,
|
|
||||||
/// Already in the blockchain.
|
|
||||||
AlreadyInChain,
|
|
||||||
/// Block or parent is known to be bad.
|
|
||||||
KnownBad,
|
|
||||||
/// Block parent is not in the chain.
|
|
||||||
UnknownParent,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Block status.
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum BlockStatus {
|
|
||||||
/// Added to the import queue.
|
|
||||||
Queued,
|
|
||||||
/// Already in the blockchain.
|
|
||||||
InChain,
|
|
||||||
/// Block or parent is known to be bad.
|
|
||||||
KnownBad,
|
|
||||||
/// Not in the queue or the blockchain.
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an instance of in-memory client.
|
|
||||||
pub fn new_in_mem<E, F>(
|
|
||||||
executor: E,
|
|
||||||
build_genesis: F
|
|
||||||
) -> error::Result<Client<in_mem::Backend, E>>
|
|
||||||
where
|
|
||||||
E: state_machine::CodeExecutor,
|
|
||||||
F: FnOnce() -> (block::Header, Vec<(Vec<u8>, Vec<u8>)>)
|
|
||||||
{
|
|
||||||
Client::new(in_mem::Backend::new(), executor, build_genesis)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, E> Client<B, E> where
|
|
||||||
B: backend::Backend,
|
|
||||||
E: state_machine::CodeExecutor,
|
|
||||||
error::Error: From<<<B as backend::Backend>::State as state_machine::backend::Backend>::Error>,
|
|
||||||
{
|
|
||||||
/// Creates new Polkadot Client with given blockchain and code executor.
|
|
||||||
pub fn new<F>(
|
|
||||||
backend: B,
|
|
||||||
executor: E,
|
|
||||||
build_genesis: F
|
|
||||||
) -> error::Result<Self>
|
|
||||||
where
|
|
||||||
F: FnOnce() -> (block::Header, Vec<(Vec<u8>, Vec<u8>)>)
|
|
||||||
{
|
|
||||||
if backend.blockchain().header(BlockId::Number(0))?.is_none() {
|
|
||||||
trace!("Empty database, writing genesis block");
|
|
||||||
let (genesis_header, genesis_store) = build_genesis();
|
|
||||||
let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?;
|
|
||||||
op.reset_storage(genesis_store.into_iter())?;
|
|
||||||
op.set_block_data(genesis_header, Some(vec![]), true)?;
|
|
||||||
backend.commit_operation(op)?;
|
|
||||||
}
|
|
||||||
Ok(Client {
|
|
||||||
backend,
|
|
||||||
executor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the state at a given block.
|
|
||||||
pub fn state_at(&self, block: &BlockId) -> error::Result<B::State> {
|
|
||||||
self.backend.state_at(*block)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expose backend reference. To be used in tests only
|
|
||||||
pub fn backend(&self) -> &B {
|
|
||||||
&self.backend
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return single storage entry of contract under given address in state in a block of given hash.
|
|
||||||
pub fn storage(&self, id: &BlockId, key: &StorageKey) -> error::Result<StorageData> {
|
|
||||||
Ok(self.state_at(id)?
|
|
||||||
.storage(&key.0)
|
|
||||||
.map(|x| StorageData(x.to_vec()))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the code at a given block.
|
|
||||||
pub fn code_at(&self, id: &BlockId) -> error::Result<Vec<u8>> {
|
|
||||||
self.storage(id, &StorageKey(b":code".to_vec())).map(|data| data.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clone a new instance of Executor.
|
|
||||||
pub fn clone_executor(&self) -> E where E: Clone {
|
|
||||||
self.executor.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current set of authorities from storage.
|
|
||||||
pub fn authorities_at(&self, id: &BlockId) -> error::Result<Vec<AuthorityId>> {
|
|
||||||
let state = self.state_at(id)?;
|
|
||||||
(0..u32::decode(&mut state.storage(b":auth:len")?).ok_or(error::ErrorKind::AuthLen)?)
|
|
||||||
.map(|i| state.storage(&i.to_keyed_vec(b":auth:"))
|
|
||||||
.map_err(|_| error::ErrorKind::Backend)
|
|
||||||
.and_then(|mut s| AuthorityId::decode(&mut s).ok_or(error::ErrorKind::Auth(i)))
|
|
||||||
.map_err(Into::into)
|
|
||||||
).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a call to a contract on top of state in a block of given hash.
|
|
||||||
///
|
|
||||||
/// No changes are made.
|
|
||||||
pub fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result<CallResult> {
|
|
||||||
let mut changes = state_machine::OverlayedChanges::default();
|
|
||||||
let return_data = state_machine::execute(
|
|
||||||
&self.state_at(id)?,
|
|
||||||
&mut changes,
|
|
||||||
&self.executor,
|
|
||||||
method,
|
|
||||||
call_data,
|
|
||||||
)?;
|
|
||||||
Ok(CallResult { return_data, changes })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up the native execution environment to call into a native runtime code.
|
|
||||||
pub fn using_environment<F: FnOnce() -> T, T>(
|
|
||||||
&self, f: F
|
|
||||||
) -> error::Result<T> {
|
|
||||||
self.using_environment_at(&BlockId::Number(self.info()?.chain.best_number), &mut Default::default(), f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up the native execution environment to call into a native runtime code.
|
|
||||||
pub fn using_environment_at<F: FnOnce() -> T, T>(
|
|
||||||
&self,
|
|
||||||
id: &BlockId,
|
|
||||||
overlay: &mut OverlayedChanges,
|
|
||||||
f: F
|
|
||||||
) -> error::Result<T> {
|
|
||||||
Ok(runtime_io::with_externalities(&mut Ext { backend: &self.state_at(id)?, overlay }, f))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new block, built on the head of the chain.
|
|
||||||
pub fn new_block(&self) -> error::Result<block_builder::BlockBuilder<B, E>> where E: Clone {
|
|
||||||
block_builder::BlockBuilder::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new block, built on top of `parent`.
|
|
||||||
pub fn new_block_at(&self, parent: &BlockId) -> error::Result<block_builder::BlockBuilder<B, E>> where E: Clone {
|
|
||||||
block_builder::BlockBuilder::at_block(parent, &self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue a block for import.
|
|
||||||
pub fn import_block(&self, header: block::Header, body: Option<block::Body>) -> error::Result<ImportResult> {
|
|
||||||
// TODO: import lock
|
|
||||||
// TODO: validate block
|
|
||||||
match self.backend.blockchain().status(BlockId::Hash(header.parent_hash))? {
|
|
||||||
blockchain::BlockStatus::InChain => (),
|
|
||||||
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut transaction = self.backend.begin_operation(BlockId::Hash(header.parent_hash))?;
|
|
||||||
let mut overlay = OverlayedChanges::default();
|
|
||||||
|
|
||||||
state_machine::execute(
|
|
||||||
transaction.state()?,
|
|
||||||
&mut overlay,
|
|
||||||
&self.executor,
|
|
||||||
"execute_block",
|
|
||||||
&block::Block { header: header.clone(), transactions: body.clone().unwrap_or_default().clone() }.encode()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let is_new_best = header.number == self.backend.blockchain().info()?.best_number + 1;
|
|
||||||
transaction.set_block_data(header, body, is_new_best)?;
|
|
||||||
transaction.set_storage(overlay.drain())?;
|
|
||||||
self.backend.commit_operation(transaction)?;
|
|
||||||
Ok(ImportResult::Queued)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get blockchain info.
|
|
||||||
pub fn info(&self) -> error::Result<ClientInfo> {
|
|
||||||
let info = self.backend.blockchain().info().map_err(|e| error::Error::from_blockchain(Box::new(e)))?;
|
|
||||||
Ok(ClientInfo {
|
|
||||||
chain: info,
|
|
||||||
best_queued_hash: None,
|
|
||||||
best_queued_number: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get block status.
|
|
||||||
pub fn block_status(&self, id: &BlockId) -> error::Result<BlockStatus> {
|
|
||||||
// TODO: more efficient implementation
|
|
||||||
match self.backend.blockchain().header(*id).map_err(|e| error::Error::from_blockchain(Box::new(e)))?.is_some() {
|
|
||||||
true => Ok(BlockStatus::InChain),
|
|
||||||
false => Ok(BlockStatus::Unknown),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get block hash by number.
|
|
||||||
pub fn block_hash(&self, block_number: block::Number) -> error::Result<Option<block::HeaderHash>> {
|
|
||||||
self.backend.blockchain().hash(block_number)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert an arbitrary block ID into a block hash.
|
|
||||||
pub fn block_hash_from_id(&self, id: &BlockId) -> error::Result<Option<block::HeaderHash>> {
|
|
||||||
match *id {
|
|
||||||
BlockId::Hash(h) => Ok(Some(h)),
|
|
||||||
BlockId::Number(n) => self.block_hash(n),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert an arbitrary block ID into a block hash.
|
|
||||||
pub fn block_number_from_id(&self, id: &BlockId) -> error::Result<Option<block::Number>> {
|
|
||||||
match *id {
|
|
||||||
BlockId::Hash(_) => Ok(self.header(id)?.map(|h| h.number)),
|
|
||||||
BlockId::Number(n) => Ok(Some(n)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get block header by id.
|
|
||||||
pub fn header(&self, id: &BlockId) -> error::Result<Option<block::Header>> {
|
|
||||||
self.backend.blockchain().header(*id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get block body by id.
|
|
||||||
pub fn body(&self, id: &BlockId) -> error::Result<Option<block::Body>> {
|
|
||||||
self.backend.blockchain().body(*id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use codec::Slicable;
|
|
||||||
use keyring::Keyring;
|
|
||||||
use primitives::block::Transaction as PrimitiveTransaction;
|
|
||||||
use test_runtime::genesismap::{GenesisConfig, additional_storage_with_genesis};
|
|
||||||
use test_runtime::{UncheckedTransaction, Transaction};
|
|
||||||
use test_runtime;
|
|
||||||
|
|
||||||
native_executor_instance!(Executor, test_runtime::api::dispatch, include_bytes!("../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm"));
|
|
||||||
|
|
||||||
fn genesis_config() -> GenesisConfig {
|
|
||||||
GenesisConfig::new_simple(vec![
|
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_genesis() -> (primitives::block::Header, Vec<(Vec<u8>, Vec<u8>)>) {
|
|
||||||
let mut storage = genesis_config().genesis_map();
|
|
||||||
let block = genesis::construct_genesis_block(&storage);
|
|
||||||
storage.extend(additional_storage_with_genesis(&block));
|
|
||||||
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn client_initialises_from_genesis_ok() {
|
|
||||||
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
|
||||||
let genesis_hash = client.block_hash(0).unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), genesis_hash);
|
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Alice.to_raw_public())).unwrap(), 1000);
|
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Ferdie.to_raw_public())).unwrap(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn authorities_call_works() {
|
|
||||||
let genesis_config = GenesisConfig::new_simple(vec![
|
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000);
|
|
||||||
|
|
||||||
let prepare_genesis = || {
|
|
||||||
let mut storage = genesis_config.genesis_map();
|
|
||||||
let block = genesis::construct_genesis_block(&storage);
|
|
||||||
storage.extend(additional_storage_with_genesis(&block));
|
|
||||||
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
|
||||||
};
|
|
||||||
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(client.info().unwrap().chain.best_number, 0);
|
|
||||||
assert_eq!(client.authorities_at(&BlockId::Number(0)).unwrap(), vec![
|
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_builder_works_with_no_transactions() {
|
|
||||||
let genesis_config = GenesisConfig::new_simple(vec![
|
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000);
|
|
||||||
|
|
||||||
let prepare_genesis = || {
|
|
||||||
let mut storage = genesis_config.genesis_map();
|
|
||||||
let block = genesis::construct_genesis_block(&storage);
|
|
||||||
storage.extend(additional_storage_with_genesis(&block));
|
|
||||||
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
|
||||||
};
|
|
||||||
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
|
||||||
|
|
||||||
let builder = client.new_block().unwrap();
|
|
||||||
let block = builder.bake().unwrap();
|
|
||||||
|
|
||||||
client.import_block(block.header, Some(block.transactions)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap(), client.block_hash(1).unwrap().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Signable {
|
|
||||||
fn signed(self) -> PrimitiveTransaction;
|
|
||||||
}
|
|
||||||
impl Signable for Transaction {
|
|
||||||
fn signed(self) -> PrimitiveTransaction {
|
|
||||||
let signature = Keyring::from_raw_public(self.from.clone()).unwrap().sign(&self.encode());
|
|
||||||
PrimitiveTransaction::decode(&mut UncheckedTransaction { signature, tx: self }.encode().as_ref()).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn block_builder_works_with_transactions() {
|
|
||||||
let genesis_config = GenesisConfig::new_simple(vec![
|
|
||||||
Keyring::Alice.to_raw_public(),
|
|
||||||
Keyring::Bob.to_raw_public(),
|
|
||||||
Keyring::Charlie.to_raw_public()
|
|
||||||
], 1000);
|
|
||||||
|
|
||||||
let prepare_genesis = || {
|
|
||||||
let mut storage = genesis_config.genesis_map();
|
|
||||||
let block = genesis::construct_genesis_block(&storage);
|
|
||||||
storage.extend(additional_storage_with_genesis(&block));
|
|
||||||
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
|
|
||||||
};
|
|
||||||
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
|
|
||||||
|
|
||||||
let mut builder = client.new_block().unwrap();
|
|
||||||
|
|
||||||
builder.push(Transaction {
|
|
||||||
from: Keyring::Alice.to_raw_public(),
|
|
||||||
to: Keyring::Ferdie.to_raw_public(),
|
|
||||||
amount: 42,
|
|
||||||
nonce: 0
|
|
||||||
}.signed()).unwrap();
|
|
||||||
let block = builder.bake().unwrap();
|
|
||||||
client.import_block(block.header, Some(block.transactions)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
|
||||||
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
|
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Alice.to_raw_public())).unwrap(), 958);
|
|
||||||
assert_eq!(client.using_environment(|| test_runtime::system::balance_of(Keyring::Ferdie.to_raw_public())).unwrap(), 42);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ use std::{error, fmt};
|
|||||||
|
|
||||||
use super::{Update, MemoryState};
|
use super::{Update, MemoryState};
|
||||||
|
|
||||||
/// Output of a commit.
|
|
||||||
pub struct Committed {}
|
|
||||||
|
|
||||||
/// A state backend is used to read state data and can have changes committed
|
/// A state backend is used to read state data and can have changes committed
|
||||||
/// to it.
|
/// to it.
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
@@ -33,7 +30,7 @@ pub trait Backend {
|
|||||||
fn storage(&self, key: &[u8]) -> Result<&[u8], Self::Error>;
|
fn storage(&self, key: &[u8]) -> Result<&[u8], Self::Error>;
|
||||||
|
|
||||||
/// Commit updates to the backend and get new state.
|
/// Commit updates to the backend and get new state.
|
||||||
fn commit<I>(&mut self, changes: I) -> Committed
|
fn commit<I>(&mut self, changes: I)
|
||||||
where I: IntoIterator<Item=Update>;
|
where I: IntoIterator<Item=Update>;
|
||||||
|
|
||||||
/// Get all key/value pairs into a Vec.
|
/// Get all key/value pairs into a Vec.
|
||||||
@@ -80,11 +77,10 @@ impl Backend for InMemory {
|
|||||||
Ok(self.inner.storage(key).unwrap_or(&[]))
|
Ok(self.inner.storage(key).unwrap_or(&[]))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit<I>(&mut self, changes: I) -> Committed
|
fn commit<I>(&mut self, changes: I)
|
||||||
where I: IntoIterator<Item=Update>
|
where I: IntoIterator<Item=Update>
|
||||||
{
|
{
|
||||||
self.inner.update(changes);
|
self.inner.update(changes);
|
||||||
Committed {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pairs(&self) -> Vec<(&[u8], &[u8])> {
|
fn pairs(&self) -> Vec<(&[u8], &[u8])> {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ mod testing;
|
|||||||
|
|
||||||
pub use testing::TestExternalities;
|
pub use testing::TestExternalities;
|
||||||
pub use ext::Ext;
|
pub use ext::Ext;
|
||||||
|
pub use backend::Backend;
|
||||||
|
|
||||||
/// Updates to be committed to the state.
|
/// Updates to be committed to the state.
|
||||||
pub type Update = (Vec<u8>, Vec<u8>);
|
pub type Update = (Vec<u8>, Vec<u8>);
|
||||||
|
|||||||
Reference in New Issue
Block a user