mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-02 02:27:24 +00:00
Optional storage entries (#75)
* Block builder (substrate) * Fix wasm build * Bulid on any block * Test for block builder. * Block import tests for client. * Tidy ups * Repotted client * Avoid pointless work * All backend stuff now manages optional storage. Also simplified a lot of the backend. * Native runtime-io now supports empty storage items. * Finish up the API transition. * Build fix. * Fix tests. * Remerge in changes to client. * Final fixes. * Unrevert typos * Remove accidentally committed change * Bring back zero copy * Fix merge.
This commit is contained in:
committed by
Robert Habermeier
parent
1d0ad42230
commit
ec9060460c
@@ -31,7 +31,7 @@ pub trait BlockImportOperation {
|
||||
/// Append block data to the transaction.
|
||||
fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, justification: Option<primitives::bft::Justification>, is_new_best: bool) -> error::Result<()>;
|
||||
/// Inject storage data into the database.
|
||||
fn set_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
|
||||
fn set_storage<I: Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>>(&mut self, changes: I) -> error::Result<()>;
|
||||
/// Inject storage data into the database.
|
||||
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
// 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::{self, block, AuthorityId};
|
||||
use primitives::block::Id as BlockId;
|
||||
use primitives::storage::{StorageKey, StorageData};
|
||||
use runtime_support::Hashable;
|
||||
use codec::{KeyedVec, Slicable};
|
||||
use state_machine::{self, Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor};
|
||||
|
||||
use backend::{self, BlockImportOperation};
|
||||
use blockchain::{self, Info as ChainInfo, Backend as ChainBackend};
|
||||
use {error, in_mem, block_builder, runtime_io, bft};
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// A header paired with a justification which has already been checked.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct JustifiedHeader {
|
||||
header: block::Header,
|
||||
justification: bft::Justification,
|
||||
}
|
||||
|
||||
impl JustifiedHeader {
|
||||
/// Deconstruct the justified header into parts.
|
||||
pub fn into_inner(self) -> (block::Header, bft::Justification) {
|
||||
(self.header, self.justification)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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![]), None, 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(StorageData(self.state_at(id)?
|
||||
.storage(&key.0)?
|
||||
.ok_or_else(|| error::ErrorKind::NoValueForKey(key.0.clone()))?
|
||||
.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::AuthLenEmpty)?).ok_or(error::ErrorKind::AuthLenInvalid)?)
|
||||
.map(|i| state.storage(&i.to_keyed_vec(b":auth:"))
|
||||
.map_err(|_| error::ErrorKind::Backend)
|
||||
.and_then(|v| v.ok_or(error::ErrorKind::AuthEmpty(i)))
|
||||
.and_then(|mut s| AuthorityId::decode(&mut s).ok_or(error::ErrorKind::AuthInvalid(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)
|
||||
}
|
||||
|
||||
/// Check a header's justification.
|
||||
pub fn check_justification(
|
||||
&self,
|
||||
header: block::Header,
|
||||
justification: bft::UncheckedJustification,
|
||||
) -> error::Result<JustifiedHeader> {
|
||||
let authorities = self.authorities_at(&BlockId::Hash(header.parent_hash))?;
|
||||
let just = bft::check_justification(&authorities[..], header.parent_hash, justification)
|
||||
.map_err(|_| error::ErrorKind::BadJustification(BlockId::Hash(header.hash())))?;
|
||||
Ok(JustifiedHeader {
|
||||
header,
|
||||
justification: just,
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue a block for import.
|
||||
pub fn import_block(
|
||||
&self,
|
||||
header: JustifiedHeader,
|
||||
body: Option<block::Body>,
|
||||
) -> error::Result<ImportResult> {
|
||||
// TODO: import lock
|
||||
// TODO: validate block
|
||||
// TODO: import justification.
|
||||
let (header, justification) = header.into_inner();
|
||||
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;
|
||||
trace!("Imported {}, (#{}), best={}", block::HeaderHash::from(header.blake2_256()), header.number, is_new_best);
|
||||
transaction.set_block_data(header, body, Some(justification.uncheck().into()), 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)
|
||||
}
|
||||
|
||||
/// Get block justification set by id.
|
||||
pub fn justification(&self, id: &BlockId) -> error::Result<Option<primitives::bft::Justification>> {
|
||||
self.backend.blockchain().justification(*id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> bft::BlockImport for Client<B, E>
|
||||
where
|
||||
B: backend::Backend,
|
||||
E: state_machine::CodeExecutor,
|
||||
error::Error: From<<B::State as state_machine::backend::Backend>::Error>
|
||||
{
|
||||
fn import_block(&self, block: block::Block, justification: bft::Justification) {
|
||||
let justified_header = JustifiedHeader {
|
||||
header: block.header,
|
||||
justification,
|
||||
};
|
||||
|
||||
let _ = self.import_block(justified_header, Some(block.transactions));
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> bft::Authorities for Client<B, E>
|
||||
where
|
||||
B: backend::Backend,
|
||||
E: state_machine::CodeExecutor,
|
||||
error::Error: From<<B::State as state_machine::backend::Backend>::Error>
|
||||
{
|
||||
fn authorities(&self, at: &BlockId) -> Result<Vec<AuthorityId>, bft::Error> {
|
||||
self.authorities_at(at).map_err(|_| bft::ErrorKind::StateUnavailable(*at).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
|
||||
// since we are in the client module we can create falsely justified
|
||||
// headers.
|
||||
// TODO: remove this in favor of custom verification pipelines for the
|
||||
// client
|
||||
fn justify(header: &block::Header) -> bft::UncheckedJustification {
|
||||
let hash = header.hash();
|
||||
let authorities = vec![
|
||||
Keyring::Alice.into(),
|
||||
Keyring::Bob.into(),
|
||||
Keyring::Charlie.into(),
|
||||
];
|
||||
|
||||
bft::UncheckedJustification {
|
||||
digest: hash,
|
||||
signatures: authorities.iter().map(|key| {
|
||||
bft::sign_message(
|
||||
bft::generic::Message::Commit(1, hash),
|
||||
key,
|
||||
header.parent_hash
|
||||
).signature
|
||||
}).collect(),
|
||||
round_number: 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[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 = genesis_config();
|
||||
|
||||
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 = genesis_config();
|
||||
|
||||
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();
|
||||
|
||||
let justification = justify(&block.header);
|
||||
let justified = client.check_justification(block.header, justification).unwrap();
|
||||
client.import_block(justified, 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();
|
||||
|
||||
let justification = justify(&block.header);
|
||||
let justified = client.check_justification(block.header, justification).unwrap();
|
||||
client.import_block(justified, 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);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
use std;
|
||||
use state_machine;
|
||||
use primitives::hexdisplay::HexDisplay;
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
@@ -45,23 +46,41 @@ error_chain! {
|
||||
display("Blockchain: {}", e),
|
||||
}
|
||||
|
||||
/// Bad justification for header.
|
||||
BadJustification(h: ::primitives::block::Id) {
|
||||
description("bad justification for header"),
|
||||
display("bad justification for header: {}", h),
|
||||
/// Attempt to read storage yet nothing set for that key.
|
||||
NoValueForKey(key: Vec<u8>) {
|
||||
description("storage doesn't contain key"),
|
||||
display("Storage does not contain the key entry: {}", HexDisplay::from(key)),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthLen {
|
||||
AuthLenEmpty {
|
||||
description("authority count state error"),
|
||||
display("Current state of blockchain has no authority count value"),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthEmpty(i: u32) {
|
||||
description("authority value state error"),
|
||||
display("Current state of blockchain has no authority value for index {}", i),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthLenInvalid {
|
||||
description("authority count state error"),
|
||||
display("Current state of blockchain has invalid authority count value"),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
Auth(i: u32) {
|
||||
AuthInvalid(i: u32) {
|
||||
description("authority value state error"),
|
||||
display("Current state of blockchain has invalid authority value for index {}", i),
|
||||
}
|
||||
|
||||
/// Bad justification for header.
|
||||
BadJustification(h: ::primitives::block::Id) {
|
||||
description("bad justification for header"),
|
||||
display("bad justification for header: {}", h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_genesis_should_work() {
|
||||
fn construct_genesis_should_work_with_native() {
|
||||
let mut storage = GenesisConfig::new_simple(
|
||||
vec![Keyring::One.to_raw_public(), Keyring::Two.to_raw_public()], 1000
|
||||
).genesis_map();
|
||||
@@ -133,6 +133,19 @@ mod tests {
|
||||
"execute_block",
|
||||
&b1data
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_genesis_should_work_with_wasm() {
|
||||
let mut storage = GenesisConfig::new_simple(
|
||||
vec![Keyring::One.to_raw_public(), Keyring::Two.to_raw_public()], 1000
|
||||
).genesis_map();
|
||||
let block = construct_genesis_block(&storage);
|
||||
let genesis_hash = block.header.blake2_256().into();
|
||||
storage.extend(additional_storage_with_genesis(&block).into_iter());
|
||||
|
||||
let backend = InMemory::from(storage);
|
||||
let (b1data, _b1hash) = block1(genesis_hash, &backend);
|
||||
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let _ = execute(
|
||||
|
||||
@@ -180,7 +180,7 @@ impl backend::BlockImportOperation for BlockImportOperation {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, changes: I) -> error::Result<()> {
|
||||
fn set_storage<I: Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>>(&mut self, changes: I) -> error::Result<()> {
|
||||
self.pending_state.commit(changes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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
|
||||
//! Substrate Client and associated logic.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
@@ -41,485 +41,7 @@ pub mod backend;
|
||||
pub mod in_mem;
|
||||
pub mod genesis;
|
||||
pub mod block_builder;
|
||||
mod client;
|
||||
|
||||
pub use client::{Client, ClientInfo, CallResult, ImportResult, BlockStatus, new_in_mem};
|
||||
pub use blockchain::Info as ChainInfo;
|
||||
|
||||
use primitives::{block, AuthorityId};
|
||||
use primitives::block::Id as BlockId;
|
||||
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};
|
||||
use runtime_support::Hashable;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// A header paired with a justification which has already been checked.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct JustifiedHeader {
|
||||
header: block::Header,
|
||||
justification: bft::Justification,
|
||||
}
|
||||
|
||||
impl JustifiedHeader {
|
||||
/// Deconstruct the justified header into parts.
|
||||
pub fn into_inner(self) -> (block::Header, bft::Justification) {
|
||||
(self.header, self.justification)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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![]), None, 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)
|
||||
}
|
||||
|
||||
/// Check a header's justification.
|
||||
pub fn check_justification(
|
||||
&self,
|
||||
header: block::Header,
|
||||
justification: bft::UncheckedJustification,
|
||||
) -> error::Result<JustifiedHeader> {
|
||||
let authorities = self.authorities_at(&BlockId::Hash(header.parent_hash))?;
|
||||
let just = bft::check_justification(&authorities[..], header.parent_hash, justification)
|
||||
.map_err(|_| error::ErrorKind::BadJustification(BlockId::Hash(header.hash())))?;
|
||||
Ok(JustifiedHeader {
|
||||
header,
|
||||
justification: just,
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue a block for import.
|
||||
pub fn import_block(
|
||||
&self,
|
||||
header: JustifiedHeader,
|
||||
body: Option<block::Body>,
|
||||
) -> error::Result<ImportResult> {
|
||||
// TODO: import lock
|
||||
// TODO: validate block
|
||||
// TODO: import justification.
|
||||
let (header, justification) = header.into_inner();
|
||||
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;
|
||||
trace!("Imported {}, (#{}), best={}", block::HeaderHash::from(header.blake2_256()), header.number, is_new_best);
|
||||
transaction.set_block_data(header, body, Some(justification.uncheck().into()), 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)
|
||||
}
|
||||
|
||||
/// Get block justification set by id.
|
||||
pub fn justification(&self, id: &BlockId) -> error::Result<Option<primitives::bft::Justification>> {
|
||||
self.backend.blockchain().justification(*id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> bft::BlockImport for Client<B, E>
|
||||
where
|
||||
B: backend::Backend,
|
||||
E: state_machine::CodeExecutor,
|
||||
error::Error: From<<B::State as state_machine::backend::Backend>::Error>
|
||||
{
|
||||
fn import_block(&self, block: block::Block, justification: bft::Justification) {
|
||||
let justified_header = JustifiedHeader {
|
||||
header: block.header,
|
||||
justification,
|
||||
};
|
||||
|
||||
let _ = self.import_block(justified_header, Some(block.transactions));
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> bft::Authorities for Client<B, E>
|
||||
where
|
||||
B: backend::Backend,
|
||||
E: state_machine::CodeExecutor,
|
||||
error::Error: From<<B::State as state_machine::backend::Backend>::Error>
|
||||
{
|
||||
fn authorities(&self, at: &BlockId) -> Result<Vec<AuthorityId>, bft::Error> {
|
||||
self.authorities_at(at).map_err(|_| bft::ErrorKind::StateUnavailable(*at).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[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())
|
||||
}
|
||||
|
||||
// since we are in the client module we can create falsely justified
|
||||
// headers.
|
||||
// TODO: remove this in favor of custom verification pipelines for the
|
||||
// client
|
||||
fn justify(header: &block::Header) -> bft::UncheckedJustification {
|
||||
let hash = header.hash();
|
||||
let authorities = vec![
|
||||
Keyring::Alice.into(),
|
||||
Keyring::Bob.into(),
|
||||
Keyring::Charlie.into(),
|
||||
];
|
||||
|
||||
bft::UncheckedJustification {
|
||||
digest: hash,
|
||||
signatures: authorities.iter().map(|key| {
|
||||
bft::sign_message(
|
||||
bft::generic::Message::Commit(1, hash),
|
||||
key,
|
||||
header.parent_hash
|
||||
).signature
|
||||
}).collect(),
|
||||
round_number: 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[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 = genesis_config();
|
||||
|
||||
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 = genesis_config();
|
||||
|
||||
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();
|
||||
|
||||
let justification = justify(&block.header);
|
||||
let justified = client.check_justification(block.header, justification).unwrap();
|
||||
client.import_block(justified, 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 = genesis_config();
|
||||
|
||||
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();
|
||||
|
||||
let justification = justify(&block.header);
|
||||
let justified = client.check_justification(block.header, justification).unwrap();
|
||||
client.import_block(justified, 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user