Block import tests for client.

This commit is contained in:
Gav
2018-02-13 15:25:42 +01:00
parent 5001151579
commit 034f3f574a
8 changed files with 159 additions and 54 deletions
+1 -1
View File
@@ -1391,8 +1391,8 @@ dependencies = [
"substrate-executor 0.1.0", "substrate-executor 0.1.0",
"substrate-keyring 0.1.0", "substrate-keyring 0.1.0",
"substrate-primitives 0.1.0", "substrate-primitives 0.1.0",
"substrate-runtime-io 0.1.0",
"substrate-runtime-support 0.1.0", "substrate-runtime-support 0.1.0",
"substrate-serializer 0.1.0",
"substrate-state-machine 0.1.0", "substrate-state-machine 0.1.0",
"substrate-test-runtime 0.1.0", "substrate-test-runtime 0.1.0",
"triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+1 -1
View File
@@ -13,8 +13,8 @@ ed25519 = { path = "../ed25519" }
substrate-codec = { path = "../codec" } substrate-codec = { path = "../codec" }
substrate-executor = { path = "../executor" } substrate-executor = { path = "../executor" }
substrate-primitives = { path = "../primitives" } substrate-primitives = { path = "../primitives" }
substrate-runtime-io = { path = "../runtime-io" }
substrate-runtime-support = { path = "../runtime-support" } substrate-runtime-support = { path = "../runtime-support" }
substrate-serializer = { path = "../serializer" }
substrate-state-machine = { path = "../state-machine" } substrate-state-machine = { path = "../state-machine" }
substrate-test-runtime = { path = "../test-runtime" } substrate-test-runtime = { path = "../test-runtime" }
substrate-keyring = { path = "../../substrate/keyring" } substrate-keyring = { path = "../../substrate/keyring" }
+8 -6
View File
@@ -21,22 +21,24 @@ use error;
use primitives::block; use primitives::block;
use blockchain::{self, BlockId}; use blockchain::{self, BlockId};
/// Block insertion transction. Keeps hold if the inserted block state and data. /// Block insertion operation. Keeps hold if the inserted block state and data.
pub trait BlockImportOperation { pub trait BlockImportOperation {
/// Associated state backend type. /// Associated state backend type.
type State: state_machine::backend::Backend; type State: state_machine::backend::Backend;
/// Returns pending state. /// Returns pending state.
fn state(&self) -> error::Result<Self::State>; fn state(&self) -> error::Result<&Self::State>;
/// Append block data to the transaction. /// Append block data to the transaction.
fn import_block(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()>; fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, 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<()>;
/// Inject storage data into the database. /// Inject storage data into the database.
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>; fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
} }
/// Client backend. Manages the data layer. /// Client backend. Manages the data layer.
pub trait Backend { pub trait Backend {
/// Associated block insertion transaction type. /// Associated block insertion operation type.
type BlockImportOperation: BlockImportOperation; type BlockImportOperation: BlockImportOperation;
/// Associated blockchain backend type. /// Associated blockchain backend type.
type Blockchain: blockchain::Backend; type Blockchain: blockchain::Backend;
@@ -44,9 +46,9 @@ pub trait Backend {
type State: state_machine::backend::Backend; type State: state_machine::backend::Backend;
/// Begin a new block insertion transaction with given parent block id. /// Begin a new block insertion transaction with given parent block id.
fn begin_transaction(&self, block: BlockId) -> error::Result<Self::BlockImportOperation>; fn begin_operation(&self, block: BlockId) -> error::Result<Self::BlockImportOperation>;
/// Commit block insertion. /// Commit block insertion.
fn commit_transaction(&self, transaction: Self::BlockImportOperation) -> error::Result<()>; fn commit_operation(&self, transaction: Self::BlockImportOperation) -> error::Result<()>;
/// Returns reference to blockchain backend. /// Returns reference to blockchain backend.
fn blockchain(&self) -> &Self::Blockchain; fn blockchain(&self) -> &Self::Blockchain;
/// Returns state backend for specified block. /// Returns state backend for specified block.
+20 -15
View File
@@ -21,13 +21,13 @@ use parking_lot::RwLock;
use state_machine; use state_machine;
use error; use error;
use backend; use backend;
use primitives; use runtime_support::Hashable;
use ser;
use primitives::block::{self, HeaderHash}; use primitives::block::{self, HeaderHash};
use blockchain::{self, BlockId, BlockStatus}; use blockchain::{self, BlockId, BlockStatus};
use state_machine::backend::Backend as StateBackend;
fn header_hash(header: &block::Header) -> block::HeaderHash { fn header_hash(header: &block::Header) -> block::HeaderHash {
primitives::hashing::blake2_256(&ser::encode(header)).into() header.blake2_256().into()
} }
struct PendingBlock { struct PendingBlock {
@@ -41,7 +41,7 @@ struct Block {
body: Option<block::Body>, body: Option<block::Body>,
} }
/// In-memory transaction. /// In-memory operation.
pub struct BlockImportOperation { pub struct BlockImportOperation {
pending_block: Option<PendingBlock>, pending_block: Option<PendingBlock>,
pending_state: state_machine::backend::InMemory, pending_state: state_machine::backend::InMemory,
@@ -156,12 +156,12 @@ impl blockchain::Backend for Blockchain {
impl backend::BlockImportOperation for BlockImportOperation { impl backend::BlockImportOperation for BlockImportOperation {
type State = state_machine::backend::InMemory; type State = state_machine::backend::InMemory;
fn state(&self) -> error::Result<Self::State> { fn state(&self) -> error::Result<&Self::State> {
Ok(self.pending_state.clone()) Ok(&self.pending_state)
} }
fn import_block(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()> { fn set_block_data(&mut self, header: block::Header, body: Option<block::Body>, is_new_best: bool) -> error::Result<()> {
assert!(self.pending_block.is_none(), "Only one block per transaction is allowed"); assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
self.pending_block = Some(PendingBlock { self.pending_block = Some(PendingBlock {
block: Block { block: Block {
header: header, header: header,
@@ -172,6 +172,11 @@ impl backend::BlockImportOperation for BlockImportOperation {
Ok(()) Ok(())
} }
fn set_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, changes: I) -> error::Result<()> {
self.pending_state.commit(changes);
Ok(())
}
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()> { fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()> {
self.pending_state = state_machine::backend::InMemory::from(iter.collect()); self.pending_state = state_machine::backend::InMemory::from(iter.collect());
Ok(()) Ok(())
@@ -214,10 +219,10 @@ impl Backend {
}; };
edit_header(&mut header); edit_header(&mut header);
let mut tx = self.begin_transaction(BlockId::Hash(best_hash)).expect("In-memory backend does not fail"); let mut tx = self.begin_operation(BlockId::Hash(best_hash)).expect("In-memory backend does not fail");
best_hash = header_hash(&header); best_hash = header_hash(&header);
tx.import_block(header, None, true).expect("In-memory backend does not fail"); tx.set_block_data(header, Some(vec![]), true).expect("In-memory backend does not fail");
self.commit_transaction(tx).expect("In-memory backend does not fail"); self.commit_operation(tx).expect("In-memory backend does not fail");
} }
} }
@@ -232,7 +237,7 @@ impl backend::Backend for Backend {
type Blockchain = Blockchain; type Blockchain = Blockchain;
type State = state_machine::backend::InMemory; type State = state_machine::backend::InMemory;
fn begin_transaction(&self, block: BlockId) -> error::Result<Self::BlockImportOperation> { fn begin_operation(&self, block: BlockId) -> error::Result<Self::BlockImportOperation> {
let state = match block { let state = match block {
BlockId::Hash(h) if h.is_zero() => Self::State::default(), BlockId::Hash(h) if h.is_zero() => Self::State::default(),
_ => self.state_at(block)?, _ => self.state_at(block)?,
@@ -244,10 +249,10 @@ impl backend::Backend for Backend {
}) })
} }
fn commit_transaction(&self, transaction: Self::BlockImportOperation) -> error::Result<()> { fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> {
if let Some(pending_block) = transaction.pending_block { if let Some(pending_block) = operation.pending_block {
let hash = header_hash(&pending_block.block.header); let hash = header_hash(&pending_block.block.header);
self.states.write().insert(hash, transaction.pending_state); self.states.write().insert(hash, operation.pending_state);
self.blockchain.insert(hash, pending_block.block.header, pending_block.block.body, pending_block.is_best); self.blockchain.insert(hash, pending_block.block.header, pending_block.block.body, pending_block.is_best);
} }
Ok(()) Ok(())
+112 -12
View File
@@ -18,13 +18,13 @@
#![warn(missing_docs)] #![warn(missing_docs)]
extern crate substrate_runtime_support as runtime_support;
extern crate substrate_runtime_io as runtime_io;
extern crate substrate_primitives as primitives; extern crate substrate_primitives as primitives;
extern crate substrate_state_machine as state_machine; extern crate substrate_state_machine as state_machine;
extern crate substrate_serializer as ser;
extern crate substrate_codec as codec; extern crate substrate_codec as codec;
#[cfg(test)] #[macro_use] extern crate substrate_executor as executor; #[cfg(test)] #[macro_use] extern crate substrate_executor as executor;
extern crate ed25519; extern crate ed25519;
#[cfg(test)] extern crate substrate_runtime_support as runtime_support;
#[cfg(test)] extern crate substrate_test_runtime as test_runtime; #[cfg(test)] extern crate substrate_test_runtime as test_runtime;
#[cfg(test)] extern crate substrate_keyring as keyring; #[cfg(test)] extern crate substrate_keyring as keyring;
@@ -51,6 +51,7 @@ use codec::{KeyedVec, Slicable};
use blockchain::Backend as BlockchainBackend; use blockchain::Backend as BlockchainBackend;
use backend::BlockImportOperation; use backend::BlockImportOperation;
use state_machine::backend::Backend as StateBackend; use state_machine::backend::Backend as StateBackend;
use state_machine::{Ext, OverlayedChanges};
/// Polkadot Client /// Polkadot Client
#[derive(Debug)] #[derive(Debug)]
@@ -136,10 +137,10 @@ impl<B, E> Client<B, E> where
if backend.blockchain().header(BlockId::Number(0))?.is_none() { if backend.blockchain().header(BlockId::Number(0))?.is_none() {
trace!("Empty database, writing genesis block"); trace!("Empty database, writing genesis block");
let (genesis_header, genesis_store) = build_genesis(); let (genesis_header, genesis_store) = build_genesis();
let mut tx = backend.begin_transaction(BlockId::Hash(block::HeaderHash::default()))?; let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?;
tx.reset_storage(genesis_store.into_iter())?; op.reset_storage(genesis_store.into_iter())?;
tx.import_block(genesis_header, None, true)?; op.set_block_data(genesis_header, Some(vec![]), true)?;
backend.commit_transaction(tx)?; backend.commit_operation(op)?;
} }
Ok(Client { Ok(Client {
backend, backend,
@@ -200,6 +201,23 @@ impl<B, E> Client<B, E> where
Ok(CallResult { return_data, changes }) 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. /// 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 { pub fn new_block(&self) -> error::Result<block_builder::BlockBuilder<B, E>> where E: Clone {
block_builder::BlockBuilder::new(self) block_builder::BlockBuilder::new(self)
@@ -219,13 +237,21 @@ impl<B, E> Client<B, E> where
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent), blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
} }
let mut transaction = self.backend.begin_transaction(BlockId::Hash(header.parent_hash))?; let mut transaction = self.backend.begin_operation(BlockId::Hash(header.parent_hash))?;
let mut _state = transaction.state()?; let mut overlay = OverlayedChanges::default();
// TODO: execute block on _state
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; let is_new_best = header.number == self.backend.blockchain().info()?.best_number + 1;
transaction.import_block(header, body, is_new_best)?; transaction.set_block_data(header, body, is_new_best)?;
self.backend.commit_transaction(transaction)?; transaction.set_storage(overlay.drain())?;
self.backend.commit_operation(transaction)?;
Ok(ImportResult::Queued) Ok(ImportResult::Queued)
} }
@@ -284,12 +310,37 @@ impl<B, E> Client<B, E> where
mod tests { mod tests {
use super::*; use super::*;
use codec::Slicable; use codec::Slicable;
use runtime_support::Hashable;
use keyring::Keyring; use keyring::Keyring;
use primitives::block::Transaction as PrimitiveTransaction;
use test_runtime::genesismap::{GenesisConfig, additional_storage_with_genesis}; use test_runtime::genesismap::{GenesisConfig, additional_storage_with_genesis};
use test_runtime::{UncheckedTransaction, Transaction};
use test_runtime; 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")); 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();
assert_eq!(client.block_hash_from_id(&BlockId::Number(0)).unwrap().unwrap().0, prepare_genesis().0.blake2_256());
}
#[test] #[test]
fn authorities_call_works() { fn authorities_call_works() {
let genesis_config = GenesisConfig::new_simple(vec![ let genesis_config = GenesisConfig::new_simple(vec![
@@ -315,7 +366,7 @@ mod tests {
} }
#[test] #[test]
fn block_builder_works() { fn block_builder_works_with_no_transactions() {
let genesis_config = GenesisConfig::new_simple(vec![ let genesis_config = GenesisConfig::new_simple(vec![
Keyring::Alice.to_raw_public(), Keyring::Alice.to_raw_public(),
Keyring::Bob.to_raw_public(), Keyring::Bob.to_raw_public(),
@@ -330,10 +381,59 @@ mod tests {
}; };
let client = new_in_mem(Executor::new(), prepare_genesis).unwrap(); let client = new_in_mem(Executor::new(), prepare_genesis).unwrap();
let genesis_hash = genesis::construct_genesis_block(&genesis_config.genesis_map()).header.blake2_256();
assert_eq!(client.using_environment(|| test_runtime::system::latest_block_hash()).unwrap().0, genesis_hash);
let builder = client.new_block().unwrap(); let builder = client.new_block().unwrap();
let block = builder.bake().unwrap(); let block = builder.bake().unwrap();
assert_eq!(block.header.parent_hash.0, genesis_hash);
client.import_block(block.header, Some(block.transactions)).unwrap(); client.import_block(block.header, Some(block.transactions)).unwrap();
assert_eq!(client.info().unwrap().chain.best_number, 1); assert_eq!(client.info().unwrap().chain.best_number, 1);
} }
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);
}
} }
@@ -57,7 +57,7 @@ impl error::Error for Void {
/// In-memory backend. Fully recomputes tries on each commit but useful for /// In-memory backend. Fully recomputes tries on each commit but useful for
/// tests. /// tests.
#[derive(Default, Clone)] #[derive(Debug, PartialEq, Default, Clone)]
pub struct InMemory { pub struct InMemory {
inner: MemoryState, // keeps all the state in memory. inner: MemoryState, // keeps all the state in memory.
} }
+12 -17
View File
@@ -42,13 +42,10 @@ pub use testing::TestExternalities;
pub use ext::Ext; pub use ext::Ext;
/// Updates to be committed to the state. /// Updates to be committed to the state.
pub enum Update { pub type Update = (Vec<u8>, Vec<u8>);
/// Set storage of object at given key -- empty is deletion.
Storage(Vec<u8>, Vec<u8>),
}
// in-memory section of the state. // in-memory section of the state.
#[derive(Default, Clone)] #[derive(Debug, PartialEq, Default, Clone)]
struct MemoryState { struct MemoryState {
storage: HashMap<Vec<u8>, Vec<u8>>, storage: HashMap<Vec<u8>, Vec<u8>>,
} }
@@ -63,15 +60,11 @@ impl MemoryState {
} }
fn update<I>(&mut self, changes: I) where I: IntoIterator<Item=Update> { fn update<I>(&mut self, changes: I) where I: IntoIterator<Item=Update> {
for update in changes { for (key, val) in changes {
match update { if val.is_empty() {
Update::Storage(key, val) => { self.storage.remove(&key);
if val.is_empty() { } else {
self.storage.remove(&key); self.storage.insert(key, val);
} else {
self.storage.insert(key, val);
}
}
} }
} }
} }
@@ -105,10 +98,12 @@ impl OverlayedChanges {
/// Commit prospective changes to state. /// Commit prospective changes to state.
pub fn commit_prospective(&mut self) { pub fn commit_prospective(&mut self) {
let storage_updates = self.prospective.storage.drain() self.committed.update(self.prospective.storage.drain());
.map(|(key, value)| Update::Storage(key, value)); }
self.committed.update(storage_updates); /// Drain prospective changes to an iterator.
pub fn drain(&mut self) -> ::std::collections::hash_map::Drain<std::vec::Vec<u8>, std::vec::Vec<u8>> {
self.committed.storage.drain()
} }
} }
@@ -62,7 +62,10 @@ macro_rules! map {
pub fn additional_storage_with_genesis(genesis_block: &Block) -> HashMap<Vec<u8>, Vec<u8>> { pub fn additional_storage_with_genesis(genesis_block: &Block) -> HashMap<Vec<u8>, Vec<u8>> {
use codec::Slicable; use codec::Slicable;
use primitives::hexdisplay::HexDisplay;
println!("genesis hash {}", HexDisplay::from(&genesis_block.header.blake2_256()));
println!("genesis {}", HexDisplay::from(&genesis_block.header.encode()));
map![ map![
twox_128(&b"latest"[..]).encode() => genesis_block.header.blake2_256().encode() twox_128(&b"latest"[..]).to_vec() => genesis_block.header.blake2_256().to_vec()
] ]
} }