Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Block builder (substrate) (#73)
Browse files Browse the repository at this point in the history
* Block builder (substrate)

* Fix wasm build

* Bulid on any block

* Test for block builder.

* Block import tests for client.

* Tidy ups

* clean up block builder instantiation

* Propert block generation for tests

* Fixed rpc tests
  • Loading branch information
gavofyork authored and rphmeier committed Feb 15, 2018
1 parent b5b1cb7 commit b059bc3
Show file tree
Hide file tree
Showing 25 changed files with 422 additions and 136 deletions.
7 changes: 6 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
6 changes: 4 additions & 2 deletions substrate/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ ed25519 = { path = "../ed25519" }
substrate-codec = { path = "../codec" }
substrate-executor = { path = "../executor" }
substrate-primitives = { path = "../primitives" }
substrate-runtime-io = { path = "../runtime-io" }
substrate-runtime-support = { path = "../runtime-support" }
substrate-serializer = { path = "../serializer" }
substrate-state-machine = { path = "../state-machine" }
substrate-test-runtime = { path = "../test-runtime" }
substrate-keyring = { path = "../../substrate/keyring" }

[dev-dependencies]
substrate-test-runtime = { path = "../test-runtime" }
14 changes: 8 additions & 6 deletions substrate/client/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,34 @@ use error;
use primitives::block;
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 {
/// Associated state backend type.
type State: state_machine::backend::Backend;

/// Returns pending state.
fn state(&self) -> error::Result<Self::State>;
fn state(&self) -> error::Result<&Self::State>;
/// 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.
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
}

/// Client backend. Manages the data layer.
pub trait Backend {
/// Associated block insertion transaction type.
/// Associated block insertion operation type.
type BlockImportOperation: BlockImportOperation;
/// Associated blockchain backend type.
type Blockchain: blockchain::Backend;
/// Associated state backend type.
type State: state_machine::backend::Backend;

/// 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.
fn commit_transaction(&self, transaction: Self::BlockImportOperation) -> error::Result<()>;
fn commit_operation(&self, transaction: Self::BlockImportOperation) -> error::Result<()>;
/// Returns reference to blockchain backend.
fn blockchain(&self) -> &Self::Blockchain;
/// Returns state backend for specified block.
Expand Down
90 changes: 90 additions & 0 deletions substrate/client/src/block_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// 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/>.

//! Utility struct to build a block.
use std::vec::Vec;
use codec::{Joiner, Slicable};
use state_machine::{self, CodeExecutor};
use primitives::{Header, Block};
use primitives::block::Transaction;
use {backend, error, BlockId, Client};
use triehash::ordered_trie_root;

/// Utility for building new (valid) blocks from a stream of transactions.
pub struct BlockBuilder<B, E> where
B: backend::Backend,
E: CodeExecutor + Clone,
error::Error: From<<<B as backend::Backend>::State as state_machine::backend::Backend>::Error>,
{
header: Header,
transactions: Vec<Transaction>,
executor: E,
state: B::State,
changes: state_machine::OverlayedChanges,
}

impl<B, E> BlockBuilder<B, E> where
B: backend::Backend,
E: CodeExecutor + Clone,
error::Error: From<<<B as backend::Backend>::State as state_machine::backend::Backend>::Error>,
{
/// Create a new instance of builder from the given client, building on the latest block.
pub fn new(client: &Client<B, E>) -> error::Result<Self> {
client.info().and_then(|i| Self::at_block(&BlockId::Hash(i.chain.best_hash), client))
}

/// Create a new instance of builder from the given client using a particular block's ID to
/// build upon.
pub fn at_block(block_id: &BlockId, client: &Client<B, E>) -> error::Result<Self> {
Ok(BlockBuilder {
header: Header {
number: client.block_number_from_id(block_id)?.ok_or(error::ErrorKind::UnknownBlock(*block_id))? + 1,
parent_hash: client.block_hash_from_id(block_id)?.ok_or(error::ErrorKind::UnknownBlock(*block_id))?,
state_root: Default::default(),
transaction_root: Default::default(),
digest: Default::default(),
},
transactions: Default::default(),
executor: client.clone_executor(),
state: client.state_at(block_id)?,
changes: Default::default(),
})
}

/// Push a transaction onto the block's list of transactions. This will ensure the transaction
/// can be validly executed (by executing it); if it is invalid, it'll be returned along with
/// the error. Otherwise, it will return a mutable reference to self (in order to chain).
pub fn push(&mut self, tx: Transaction) -> error::Result<()> {
let output = state_machine::execute(&self.state, &mut self.changes, &self.executor, "execute_transaction",
&vec![].and(&self.header).and(&tx))?;
self.header = Header::decode(&mut &output[..]).expect("Header came straight out of runtime so must be valid");
self.transactions.push(tx);
Ok(())
}

/// Consume the builder to return a valid `Block` containing all pushed transactions.
pub fn bake(mut self) -> error::Result<Block> {
self.header.transaction_root = ordered_trie_root(self.transactions.iter().map(Slicable::encode)).0.into();
let output = state_machine::execute(&self.state, &mut self.changes, &self.executor, "finalise_block",
&self.header.encode())?;
self.header = Header::decode(&mut &output[..]).expect("Header came straight out of runtime so must be valid");
Ok(Block {
header: self.header,
transactions: self.transactions,
})
}
}
62 changes: 17 additions & 45 deletions substrate/client/src/in_mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ use parking_lot::RwLock;
use state_machine;
use error;
use backend;
use primitives;
use ser;
use runtime_support::Hashable;
use primitives::block::{self, HeaderHash};
use blockchain::{self, BlockId, BlockStatus};
use state_machine::backend::Backend as StateBackend;

fn header_hash(header: &block::Header) -> block::HeaderHash {
primitives::hashing::blake2_256(&ser::encode(header)).into()
header.blake2_256().into()
}

struct PendingBlock {
Expand All @@ -41,7 +41,7 @@ struct Block {
body: Option<block::Body>,
}

/// In-memory transaction.
/// In-memory operation.
pub struct BlockImportOperation {
pending_block: Option<PendingBlock>,
pending_state: state_machine::backend::InMemory,
Expand Down Expand Up @@ -156,12 +156,12 @@ impl blockchain::Backend for Blockchain {
impl backend::BlockImportOperation for BlockImportOperation {
type State = state_machine::backend::InMemory;

fn state(&self) -> error::Result<Self::State> {
Ok(self.pending_state.clone())
fn state(&self) -> error::Result<&Self::State> {
Ok(&self.pending_state)
}

fn import_block(&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");
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 operation is allowed");
self.pending_block = Some(PendingBlock {
block: Block {
header: header,
Expand All @@ -172,6 +172,11 @@ impl backend::BlockImportOperation for BlockImportOperation {
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<()> {
self.pending_state = state_machine::backend::InMemory::from(iter.collect());
Ok(())
Expand All @@ -192,47 +197,14 @@ impl Backend {
blockchain: Blockchain::new(),
}
}

/// Generate and import a sequence of blocks. A user supplied function is allowed to modify each block header. Useful for testing.
pub fn generate_blocks<F>(&self, count: usize, edit_header: F) where F: Fn(&mut block::Header) {
use backend::{Backend, BlockImportOperation};
let info = blockchain::Backend::info(&self.blockchain).expect("In-memory backend never fails");
let mut best_num = info.best_number;
let mut best_hash = info.best_hash;
let state_root = blockchain::Backend::header(&self.blockchain, BlockId::Hash(best_hash))
.expect("In-memory backend never fails")
.expect("Best header always exists in the blockchain")
.state_root;
for _ in 0 .. count {
best_num = best_num + 1;
let mut header = block::Header {
parent_hash: best_hash,
number: best_num,
state_root: state_root,
transaction_root: Default::default(),
digest: Default::default(),
};
edit_header(&mut header);

let mut tx = self.begin_transaction(BlockId::Hash(best_hash)).expect("In-memory backend does not fail");
best_hash = header_hash(&header);
tx.import_block(header, None, true).expect("In-memory backend does not fail");
self.commit_transaction(tx).expect("In-memory backend does not fail");
}
}

/// Generate and import a sequence of blocks. Useful for testing.
pub fn push_blocks(&self, count: usize) {
self.generate_blocks(count, |_| {})
}
}

impl backend::Backend for Backend {
type BlockImportOperation = BlockImportOperation;
type Blockchain = Blockchain;
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 {
BlockId::Hash(h) if h.is_zero() => Self::State::default(),
_ => self.state_at(block)?,
Expand All @@ -244,10 +216,10 @@ impl backend::Backend for Backend {
})
}

fn commit_transaction(&self, transaction: Self::BlockImportOperation) -> error::Result<()> {
if let Some(pending_block) = transaction.pending_block {
fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> {
if let Some(pending_block) = operation.pending_block {
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);
}
Ok(())
Expand Down
Loading

0 comments on commit b059bc3

Please sign in to comment.